From 124e49b9381d2e0995a040bea5eb5e5c56e165a5 Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 9 Jun 2026 23:51:24 +0900 Subject: [PATCH] =?UTF-8?q?refactor(tools):=20.mjs=EB=A5=BC=20=EC=A3=BC?= =?UTF-8?q?=EC=B2=B4=EB=B3=84=20=ED=8F=B4=EB=8D=94=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A5=98=20+=20=EC=B9=B4=EB=A9=94=EB=9D=BC/=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=A0=9C=EC=96=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- README.md | 4 +- RootDesk/MyDesk/MapCamera.codeblock | 2 +- RootDesk/MyDesk/PlayerLock.codeblock | 60 +++++++++++ map/map01.map | 6 +- map/map02.map | 6 +- map/map03.map | 6 +- map/map04.map | 6 +- map/map05.map | 6 +- map/map06.map | 6 +- map/map07.map | 6 +- map/map08.map | 6 +- map/map09.map | 6 +- map/map10.map | 6 +- map/map11.map | 6 +- tools/{ => balance}/sim-balance.mjs | 2 +- tools/{ => balance}/sim-balance.test.mjs | 0 tools/{ => camera}/gen-camera.mjs | 12 +-- tools/{ => deck}/gen-cardhand.mjs | 0 tools/{ => deck}/gen-slaydeck.mjs | 0 tools/{ => map}/gen-maps.mjs | 2 +- tools/{ => monster}/freeze-turn-monsters.mjs | 0 tools/{ => player}/freeze-turn-player.mjs | 0 tools/player/gen-player-lock.mjs | 101 +++++++++++++++++++ 23 files changed, 223 insertions(+), 26 deletions(-) create mode 100644 RootDesk/MyDesk/PlayerLock.codeblock rename tools/{ => balance}/sim-balance.mjs (98%) rename tools/{ => balance}/sim-balance.test.mjs (100%) rename tools/{ => camera}/gen-camera.mjs (94%) rename tools/{ => deck}/gen-cardhand.mjs (100%) rename tools/{ => deck}/gen-slaydeck.mjs (100%) rename tools/{ => map}/gen-maps.mjs (99%) rename tools/{ => monster}/freeze-turn-monsters.mjs (100%) rename tools/{ => player}/freeze-turn-player.mjs (100%) create mode 100644 tools/player/gen-player-lock.mjs diff --git a/README.md b/README.md index 80fa45e..a5dbb92 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ slaymaple/ ## 게임 프레임워크 현황 -현재 전투는 `Global/common.gamelogic`의 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작합니다. 모든 카드/덱/전투 관련 산출물(`ui/DefaultGroup.ui` · `RootDesk/MyDesk/SlayDeckController.codeblock` · `common.gamelogic`)은 **`tools/gen-slaydeck.mjs` 단일 소스에서 생성**됩니다(직접 편집 금지, 결정적 출력). +현재 전투는 `Global/common.gamelogic`의 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작합니다. 모든 카드/덱/전투 관련 산출물(`ui/DefaultGroup.ui` · `RootDesk/MyDesk/SlayDeckController.codeblock` · `common.gamelogic`)은 **`tools/deck/gen-slaydeck.mjs` 단일 소스에서 생성**됩니다(직접 편집 금지, 결정적 출력). | 컴포넌트 | 상태 | 역할 | |---|---|---| @@ -126,7 +126,7 @@ c:StartCombat() -- 전투 재시작(상태 초기화) - [x] HP·방어도·에너지·적 의도·손패 카드를 렌더링하는 전투 UI **(완료 — `SlayDeckController` + CombatHud)** - [x] 카드 사용이 실제 데미지/방어/적 의도/승패에 반영되는 단일 전투 루프 **(완료)** - [ ] 카드/적 데이터를 `data/cards.json` · `data/enemies.json`로 외부화 (D) -- [ ] 전투를 N회 자동 시뮬레이션하는 밸런스 검증 도구 `tools/sim-balance.mjs` (F, D 선행) +- [ ] 전투를 N회 자동 시뮬레이션하는 밸런스 검증 도구 `tools/balance/sim-balance.mjs` (F, D 선행) - [ ] 전투/엘리트/상점/휴식/이벤트/보스 노드를 가진 맵 노드 UI (E) - [ ] `OnCombatStart` / `OnCardPlayed` / `OnTurnStart` / `OnCombatReward` 훅을 가진 유물 시스템 (E) - [ ] 적 행동 패턴을 데이터로 정의 (현재 단순 결정적 의도 사이클 → 무브셋) diff --git a/RootDesk/MyDesk/MapCamera.codeblock b/RootDesk/MyDesk/MapCamera.codeblock index 0f0fe49..1d0744d 100644 --- a/RootDesk/MyDesk/MapCamera.codeblock +++ b/RootDesk/MyDesk/MapCamera.codeblock @@ -47,7 +47,7 @@ "Name": null }, "Arguments": [], - "Code": "self.CamTries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.CamTries = self.CamTries + 1\n\tlocal cam = nil\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tcam = lp.CameraComponent\n\tend\n\tif cam == nil then\n\t\tcam = _CameraService:GetCurrentCameraComponent()\n\tend\n\tif cam ~= nil then\n\t\tcam.ZoomRatio = 90\n\t\tcam.ScreenOffset = Vector2(0.5, 0.655)\n\t\tcam.ConfineCameraArea = true\n\t\tcam.CameraOffset = Vector2(1.5, -1)\n\tend\n\tlocal pc = nil\n\tif lp ~= nil then\n\t\tpc = lp.PlayerControllerComponent\n\t\tif pc ~= nil then\n\t\t\tpc.LookDirectionX = 1\n\t\t\tpc.FixedLookAt = true\n\t\t\tpc.Enable = false\n\t\tend\n\tend\n\tif cam ~= nil and pc ~= nil then\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.CamTries > 30 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)", + "Code": "self.CamTries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.CamTries = self.CamTries + 1\n\tlocal cam = nil\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tcam = lp.CameraComponent\n\tend\n\tif cam == nil then\n\t\tcam = _CameraService:GetCurrentCameraComponent()\n\tend\n\tif cam ~= nil then\n\t\tcam.ZoomRatio = 90\n\t\tcam.ScreenOffset = Vector2(0.5, 0.655)\n\t\tcam.ConfineCameraArea = true\n\t\tcam.CameraOffset = Vector2(1.5, -1)\n\tend\n\tif cam ~= nil then\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.CamTries > 30 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/RootDesk/MyDesk/PlayerLock.codeblock b/RootDesk/MyDesk/PlayerLock.codeblock new file mode 100644 index 0000000..bc566d5 --- /dev/null +++ b/RootDesk/MyDesk/PlayerLock.codeblock @@ -0,0 +1,60 @@ +{ + "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": [ + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "LockTries" + } + ], + "Methods": [ + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "self.LockTries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.LockTries = self.LockTries + 1\n\tlocal pc = nil\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tpc = lp.PlayerControllerComponent\n\tend\n\tif pc ~= nil then\n\t\tpc.LookDirectionX = 1\n\t\tpc.FixedLookAt = true\n\t\tpc.Enable = false\n\tend\n\tif pc ~= nil then\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.LockTries > 30 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnBeginPlay" + } + ], + "EntityEventHandlers": [] + } + } +} \ No newline at end of file diff --git a/map/map01.map b/map/map01.map index 01f5e66..504f38c 100644 --- a/map/map01.map +++ b/map/map01.map @@ -16,7 +16,7 @@ { "id": "bdadf19a-cc27-4a45-99c6-7a439c858a1b", "path": "/maps/map01", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map01", "path": "/maps/map01", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map02.map b/map/map02.map index 03b2519..3c65a85 100644 --- a/map/map02.map +++ b/map/map02.map @@ -16,7 +16,7 @@ { "id": "000007d0-0000-4000-8000-0000000007d0", "path": "/maps/map02", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map02", "path": "/maps/map02", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map03.map b/map/map03.map index c3ba5cd..c246154 100644 --- a/map/map03.map +++ b/map/map03.map @@ -16,7 +16,7 @@ { "id": "00000bb8-0000-4000-8000-000000000bb8", "path": "/maps/map03", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map03", "path": "/maps/map03", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map04.map b/map/map04.map index 505452e..e5a5efc 100644 --- a/map/map04.map +++ b/map/map04.map @@ -16,7 +16,7 @@ { "id": "00000fa0-0000-4000-8000-000000000fa0", "path": "/maps/map04", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map04", "path": "/maps/map04", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map05.map b/map/map05.map index 28eec51..8dd6249 100644 --- a/map/map05.map +++ b/map/map05.map @@ -16,7 +16,7 @@ { "id": "00001388-0000-4000-8000-000000001388", "path": "/maps/map05", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map05", "path": "/maps/map05", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map06.map b/map/map06.map index d556799..aebd0ad 100644 --- a/map/map06.map +++ b/map/map06.map @@ -16,7 +16,7 @@ { "id": "00001770-0000-4000-8000-000000001770", "path": "/maps/map06", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map06", "path": "/maps/map06", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map07.map b/map/map07.map index 093d2c9..670b28a 100644 --- a/map/map07.map +++ b/map/map07.map @@ -16,7 +16,7 @@ { "id": "00001b58-0000-4000-8000-000000001b58", "path": "/maps/map07", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map07", "path": "/maps/map07", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map08.map b/map/map08.map index 7f6699e..3d63dd9 100644 --- a/map/map08.map +++ b/map/map08.map @@ -16,7 +16,7 @@ { "id": "00001f40-0000-4000-8000-000000001f40", "path": "/maps/map08", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map08", "path": "/maps/map08", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map09.map b/map/map09.map index da59f4b..dbcbeb0 100644 --- a/map/map09.map +++ b/map/map09.map @@ -16,7 +16,7 @@ { "id": "00002328-0000-4000-8000-000000002328", "path": "/maps/map09", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map09", "path": "/maps/map09", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map10.map b/map/map10.map index 140abdf..e4d0a3e 100644 --- a/map/map10.map +++ b/map/map10.map @@ -16,7 +16,7 @@ { "id": "00002710-0000-4000-8000-000000002710", "path": "/maps/map10", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map10", "path": "/maps/map10", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/map/map11.map b/map/map11.map index de3fb5d..4cecfcf 100644 --- a/map/map11.map +++ b/map/map11.map @@ -16,7 +16,7 @@ { "id": "00002af8-0000-4000-8000-000000002af8", "path": "/maps/map11", - "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera", + "componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock", "jsonString": { "name": "map11", "path": "/maps/map11", @@ -1107,6 +1107,10 @@ { "@type": "script.MapCamera", "Enable": true + }, + { + "@type": "script.PlayerLock", + "Enable": true } ], "@version": 1 diff --git a/tools/sim-balance.mjs b/tools/balance/sim-balance.mjs similarity index 98% rename from tools/sim-balance.mjs rename to tools/balance/sim-balance.mjs index 6dba43f..afb81d2 100644 --- a/tools/sim-balance.mjs +++ b/tools/balance/sim-balance.mjs @@ -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'; diff --git a/tools/sim-balance.test.mjs b/tools/balance/sim-balance.test.mjs similarity index 100% rename from tools/sim-balance.test.mjs rename to tools/balance/sim-balance.test.mjs diff --git a/tools/gen-camera.mjs b/tools/camera/gen-camera.mjs similarity index 94% rename from tools/gen-camera.mjs rename to tools/camera/gen-camera.mjs index d28df39..734c0e4 100644 --- a/tools/gen-camera.mjs +++ b/tools/camera/gen-camera.mjs @@ -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) diff --git a/tools/gen-cardhand.mjs b/tools/deck/gen-cardhand.mjs similarity index 100% rename from tools/gen-cardhand.mjs rename to tools/deck/gen-cardhand.mjs diff --git a/tools/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs similarity index 100% rename from tools/gen-slaydeck.mjs rename to tools/deck/gen-slaydeck.mjs diff --git a/tools/gen-maps.mjs b/tools/map/gen-maps.mjs similarity index 99% rename from tools/gen-maps.mjs rename to tools/map/gen-maps.mjs index c8ff8de..1eb88e2 100644 --- a/tools/gen-maps.mjs +++ b/tools/map/gen-maps.mjs @@ -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); diff --git a/tools/freeze-turn-monsters.mjs b/tools/monster/freeze-turn-monsters.mjs similarity index 100% rename from tools/freeze-turn-monsters.mjs rename to tools/monster/freeze-turn-monsters.mjs diff --git a/tools/freeze-turn-player.mjs b/tools/player/freeze-turn-player.mjs similarity index 100% rename from tools/freeze-turn-player.mjs rename to tools/player/freeze-turn-player.mjs diff --git a/tools/player/gen-player-lock.mjs b/tools/player/gen-player-lock.mjs new file mode 100644 index 0000000..90ebb16 --- /dev/null +++ b/tools/player/gen-player-lock.mjs @@ -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(', '));