Files
maplecontest/tools/player/gen-player-lock.mjs
gahusb 989e3fe000 fix(lobby): 플레이테스트 — 이동 복원에 InputSpeed 필수 + FixedLookAt int 타입 (P15)
메이커 실측으로 확인:
- 이동에는 RigidbodyComponent.WalkAcceleration과 MovementComponent.InputSpeed가 둘 다 양수여야 함(WalkAccel만으론 안 걸림). LobbyMobility에 InputSpeed=5·JumpForce=5 추가.
- pc.FixedLookAt은 boolean이 아니라 int32 → false→0 (빌드 에러 해소).
- PlayerLock에 InputSpeed/JumpForce=0 대칭 재잠금 추가(전투맵 누설 방어).
- NPC 베이스 모델 inheritance 경고는 비치명적이라 proven-good(모델 유지) 결정 주석화.

검증: 로비 이동·점프, NpcCodex 근접(d=1.10<1.2)·↑키→카드 도감, 런 시작→map01 텔레포트+이동 잠금(InputSpeed=0), 로비 복귀→이동 재해제 전부 정상.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:50:51 +09:00

106 lines
3.7 KiB
JavaScript

import { readFileSync, writeFileSync } from 'node:fs';
// 플레이어 입력 잠금: 맵 로드 시 LocalPlayer의 PlayerControllerComponent를 런타임 설정.
// (gen-camera.mjs에서 분리 — 카메라 제어와 플레이어 제어를 주체별로 나눔)
// · 입력 차단(턴제 전투) · 시선을 오른쪽(전투 포메이션 방향)으로 고정
// 정적 이동 차단은 freeze-turn-player.mjs(Global/DefaultPlayer.model)가 담당하며, 이 스크립트는 런타임 컨트롤러를 제어한다.
const LOOK_DIRECTION_X = 1; // 1 = 오른쪽(몬스터가 배치된 전투 포메이션 방향)
const FIXED_LOOK_AT = true; // 바라보는 방향 고정
const CONTROLLER_ENABLE = false; // 플레이어 입력 차단
const MAP_NUMBERS = Array.from({ length: 5 }, (_, i) => i + 1); // map01~05
function prop(Type, Name, DefaultValue = 'nil') {
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
}
function method(Name, Code, Arguments = [], ExecSpace = 6) {
return {
Return: { Type: 'void', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: null },
Arguments,
Code,
Scope: 2,
ExecSpace,
Attributes: [],
Name,
};
}
function writeCodeblock() {
const cb = {
Id: '',
GameId: '',
EntryKey: 'codeblock://playerlock',
ContentType: 'x-mod/codeblock',
Content: '',
Usage: 0,
UsePublish: 1,
UseService: 0,
CoreVersion: '26.5.0.0',
StudioVersion: '',
DynamicLoading: 0,
ContentProto: {
Use: 'Json',
Json: {
CoreVersion: { Major: 0, Minor: 2 },
ScriptVersion: { Major: 1, Minor: 0 },
Description: '',
Id: 'PlayerLock',
Language: 1,
Name: 'PlayerLock',
Type: 1,
Source: 0,
Target: null,
Properties: [prop('number', 'LockTries', '0')],
Methods: [
method('OnBeginPlay', `self.LockTries = 0
local eventId = 0
local function apply()
self.LockTries = self.LockTries + 1
local pc = nil
local lp = _UserService.LocalPlayer
if lp ~= nil then
pc = lp.PlayerControllerComponent
end
if pc ~= nil then
pc.LookDirectionX = ${LOOK_DIRECTION_X}
pc.FixedLookAt = ${FIXED_LOOK_AT}
pc.Enable = ${CONTROLLER_ENABLE}
end
if lp ~= nil then
if lp.RigidbodyComponent ~= nil then lp.RigidbodyComponent.WalkAcceleration = 0 end
if lp.MovementComponent ~= nil then lp.MovementComponent.InputSpeed = 0; lp.MovementComponent.JumpForce = 0 end
end
if pc ~= nil then
_TimerService:ClearTimer(eventId)
elseif self.LockTries > 30 then
_TimerService:ClearTimer(eventId)
end
end
eventId = _TimerService:SetTimerRepeat(apply, 0.1)`),
],
EntityEventHandlers: [],
},
},
};
writeFileSync('RootDesk/MyDesk/PlayerLock.codeblock', JSON.stringify(cb, null, 2), 'utf8');
}
function patchMap(nn) {
const tag = String(nn).padStart(2, '0');
const file = `map/map${tag}.map`;
const map = JSON.parse(readFileSync(file, 'utf8'));
const root = map.ContentProto.Entities.find((e) => e.path === `/maps/map${tag}`);
if (!root) throw new Error(`[gen-player-lock] 맵 루트 없음: ${file}`);
// idempotent: 기존 script.PlayerLock 제거 후 재추가
root.jsonString['@components'] = root.jsonString['@components'].filter((c) => c['@type'] !== 'script.PlayerLock');
root.jsonString['@components'].push({ '@type': 'script.PlayerLock', Enable: true });
const names = (root.componentNames || '').split(',').filter((s) => s && s !== 'script.PlayerLock');
names.push('script.PlayerLock');
root.componentNames = names.join(',');
writeFileSync(file, JSON.stringify(map, null, 2), 'utf8');
return `map${tag}`;
}
writeCodeblock();
const patched = MAP_NUMBERS.map(patchMap);
console.log('PlayerLock codeblock written; patched maps:', patched.join(', '));