refactor(tools): .mjs를 주체별 폴더로 분류 + 카메라/플레이어 제어 분리
- tools/{player,monster,camera,map,deck,balance}/ 로 8개 스크립트 분류 (git mv 이력 보존)
- gen-camera의 플레이어 입력 차단·시선 고정을 tools/player/gen-player-lock.mjs(PlayerLock 코드블록)로 분리
- MapCamera 코드블록은 카메라 속성 전용으로 정리, 11개 맵 루트에 script.PlayerLock 부착
- README 및 스크립트 주석의 도구 경로 갱신
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// AI 전투 밸런스 시뮬레이터 — 오프라인 몬테카를로.
|
||||
// ⚠️ 전투 규칙은 tools/gen-slaydeck.mjs 의 Lua(SlayDeckController)와 동기화 유지할 것.
|
||||
// ⚠️ 전투 규칙은 tools/deck/gen-slaydeck.mjs 의 Lua(SlayDeckController)와 동기화 유지할 것.
|
||||
// (데이터는 data/*.json 공유, 규칙 로직은 JS로 중복 재현)
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
// 맵별 고정 카메라: 맵 로드 시 플레이어 CameraComponent를 data/camera.json 값으로 설정.
|
||||
// 새 CameraComponent를 만들지 않고(엔진 소유) 기존 카메라 속성만 런타임 설정한다.
|
||||
// 플레이어 입력 차단·시선 고정은 tools/player/gen-player-lock.mjs(script.PlayerLock)로 분리됨.
|
||||
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
||||
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
||||
|
||||
@@ -65,16 +66,7 @@ local function apply()
|
||||
cam.ConfineCameraArea = ${CAM.confineCameraArea}
|
||||
cam.CameraOffset = Vector2(${CAM.cameraOffsetX}, ${CAM.cameraOffsetY})
|
||||
end
|
||||
local pc = nil
|
||||
if lp ~= nil then
|
||||
pc = lp.PlayerControllerComponent
|
||||
if pc ~= nil then
|
||||
pc.LookDirectionX = 1
|
||||
pc.FixedLookAt = true
|
||||
pc.Enable = false
|
||||
end
|
||||
end
|
||||
if cam ~= nil and pc ~= nil then
|
||||
if cam ~= nil then
|
||||
_TimerService:ClearTimer(eventId)
|
||||
elseif self.CamTries > 30 then
|
||||
_TimerService:ClearTimer(eventId)
|
||||
@@ -133,7 +133,7 @@ function buildMap(nn) {
|
||||
return `map${tag}`;
|
||||
}
|
||||
|
||||
// 인자: 생성할 맵 번호(미지정 시 전체). 예: node tools/gen-maps.mjs 2
|
||||
// 인자: 생성할 맵 번호(미지정 시 전체). 예: node tools/map/gen-maps.mjs 2
|
||||
const arg = process.argv[2];
|
||||
const targets = arg ? [Number(arg)] : MAP_NUMBERS;
|
||||
const made = targets.map(buildMap);
|
||||
101
tools/player/gen-player-lock.mjs
Normal file
101
tools/player/gen-player-lock.mjs
Normal file
@@ -0,0 +1,101 @@
|
||||
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: 11 }, (_, i) => i + 1); // map01~11
|
||||
|
||||
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 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(', '));
|
||||
Reference in New Issue
Block a user