Merge pull request '.mjs 주체별 폴더 분류 + 카메라/플레이어 제어 분리' (#22) from feature/map-camera into main
Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
20
README.md
20
README.md
@@ -62,13 +62,13 @@ slaymaple/
|
|||||||
│ └── RectTileData_Henesys.tileset
|
│ └── RectTileData_Henesys.tileset
|
||||||
├── map/
|
├── map/
|
||||||
│ └── map01.map ~ map11.map # 맵 11종 (공식 배경 + STS풍 우측 배치)
|
│ └── map01.map ~ map11.map # 맵 11종 (공식 배경 + STS풍 우측 배치)
|
||||||
├── tools/ # 결정적 생성기·도구 (단일 소스)
|
├── tools/ # 결정적 생성기·도구 (주체별 폴더, 단일 소스)
|
||||||
│ ├── gen-slaydeck.mjs # ★ 게임 전체 생성: 카드/덱·맵·상점·유물·메인메뉴 UI + SlayDeckController + common
|
│ ├── deck/ # gen-slaydeck.mjs(★게임 전체 생성: 카드/덱·맵·상점·유물·메인메뉴 UI+SlayDeckController+common) · gen-cardhand.mjs(손패 초기 생성)
|
||||||
│ ├── gen-cardhand.mjs # 손패 카드 엔티티 초기 생성
|
│ ├── map/ # gen-maps.mjs(맵 생성)
|
||||||
│ ├── gen-maps.mjs # 맵 생성
|
│ ├── camera/ # gen-camera.mjs(맵별 고정 카메라 codeblock)
|
||||||
│ ├── sim-balance.mjs # AI 전투 밸런스 시뮬레이터(몬테카를로) + sim-balance.test.mjs
|
│ ├── player/ # freeze-turn-player.mjs(이동 정지) · gen-player-lock.mjs(입력 차단·시선 고정 codeblock)
|
||||||
│ ├── freeze-turn-monsters.mjs # 턴전투용 필드 몬스터 AI/이동 정지 패치
|
│ ├── monster/ # freeze-turn-monsters.mjs(필드 몬스터 AI/이동 정지)
|
||||||
│ └── freeze-turn-player.mjs # 턴전투용 플레이어 이동 정지 패치
|
│ └── balance/ # sim-balance.mjs(밸런스 시뮬·몬테카를로) · sim-balance.test.mjs
|
||||||
├── ui/ # UI 그룹 (Default / Popup / Toast)
|
├── ui/ # UI 그룹 (Default / Popup / Toast)
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── slaymaple_basic_framework.md # 전투 프레임워크 설계 문서
|
│ ├── slaymaple_basic_framework.md # 전투 프레임워크 설계 문서
|
||||||
@@ -82,7 +82,7 @@ slaymaple/
|
|||||||
|
|
||||||
## 게임 프레임워크 현황
|
## 게임 프레임워크 현황
|
||||||
|
|
||||||
**STS풍 덱빌더 런이 end-to-end로 완성**됐습니다 — 메인 메뉴 → 분기 맵 → 전투/엘리트/상점/휴식 → 카드 보상·덱 성장 → 유물 → 보스 → 다음 막 → 런 클리어. 게임 전체는 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작하며, 모든 산출물(`ui/DefaultGroup.ui` · `SlayDeckController.codeblock` · `common.gamelogic`)은 **`tools/gen-slaydeck.mjs` 단일 소스에서 생성**됩니다(직접 편집 금지, 결정적 출력). 게임 데이터는 **`data/*.json`** 가 단일 소스.
|
**STS풍 덱빌더 런이 end-to-end로 완성**됐습니다 — 메인 메뉴 → 분기 맵 → 전투/엘리트/상점/휴식 → 카드 보상·덱 성장 → 유물 → 보스 → 다음 막 → 런 클리어. 게임 전체는 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작하며, 모든 산출물(`ui/DefaultGroup.ui` · `SlayDeckController.codeblock` · `common.gamelogic`)은 **`tools/deck/gen-slaydeck.mjs` 단일 소스에서 생성**됩니다(직접 편집 금지, 결정적 출력). 게임 데이터는 **`data/*.json`** 가 단일 소스.
|
||||||
|
|
||||||
### 구현된 기능
|
### 구현된 기능
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ slaymaple/
|
|||||||
| **상점/휴식** | 상점=골드로 카드·유물 구매. 휴식=HP 회복 |
|
| **상점/휴식** | 상점=골드로 카드·유물 구매. 휴식=HP 회복 |
|
||||||
| **유물** | 훅 패시브(`combatStart`/`turnStart`/`cardPlayed`/`combatReward`). 획득 3경로(시작·엘리트 승리·상점). 유물 4종 |
|
| **유물** | 훅 패시브(`combatStart`/`turnStart`/`cardPlayed`/`combatReward`). 획득 3경로(시작·엘리트 승리·상점). 유물 4종 |
|
||||||
| **멀티 act** | 보스 클리어→다음 막(적 스케일 `1+(막-1)*0.6`), 최종 막 보스에서 런 클리어. 막 수 3 |
|
| **멀티 act** | 보스 클리어→다음 막(적 스케일 `1+(막-1)*0.6`), 최종 막 보스에서 런 클리어. 막 수 3 |
|
||||||
| **밸런스 시뮬** | `tools/sim-balance.mjs` — 몬테카를로 N회 전투로 승률·턴·OP 카드 리포트 |
|
| **밸런스 시뮬** | `tools/balance/sim-balance.mjs` — 몬테카를로 N회 전투로 승률·턴·OP 카드 리포트 |
|
||||||
| **턴전투 freeze** | 카드 전투 중 필드 몬스터/플레이어 이동·AI 정지(`freeze-turn-*.mjs`) |
|
| **턴전투 freeze** | 카드 전투 중 필드 몬스터/플레이어 이동·AI 정지(`freeze-turn-*.mjs`) |
|
||||||
|
|
||||||
> ⚠️ 플레이어 HP(80)·적 수치·골드/카드값(15/30/유물60)·막 배율 등은 **밸런싱 미조정 placeholder**입니다. 추후 카드·적은 **메이플스토리 IP**에 맞춰 디벨롭 예정이며, 밸런싱은 `sim-balance.mjs`로 검증합니다.
|
> ⚠️ 플레이어 HP(80)·적 수치·골드/카드값(15/30/유물60)·막 배율 등은 **밸런싱 미조정 placeholder**입니다. 추후 카드·적은 **메이플스토리 IP**에 맞춰 디벨롭 예정이며, 밸런싱은 `sim-balance.mjs`로 검증합니다.
|
||||||
@@ -113,7 +113,7 @@ c:PickReward(1) -- 보상 카드 1택(0=건너뛰기)
|
|||||||
c:BuyCard(1) / c:BuyRelic() -- 상점 구매
|
c:BuyCard(1) / c:BuyRelic() -- 상점 구매
|
||||||
```
|
```
|
||||||
|
|
||||||
밸런스 검증: `node tools/sim-balance.mjs [N] [--seed S]` · 테스트: `node --test tools/sim-balance.test.mjs`.
|
밸런스 검증: `node tools/balance/sim-balance.mjs [N] [--seed S]` · 테스트: `node --test tools/balance/sim-balance.test.mjs`.
|
||||||
상세 설계는 [`docs/slaymaple_basic_framework.md`](docs/slaymaple_basic_framework.md) 및 `docs/superpowers/specs/` 참조.
|
상세 설계는 [`docs/slaymaple_basic_framework.md`](docs/slaymaple_basic_framework.md) 및 `docs/superpowers/specs/` 참조.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
"Name": null
|
"Name": null
|
||||||
},
|
},
|
||||||
"Arguments": [],
|
"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,
|
"Scope": 2,
|
||||||
"ExecSpace": 6,
|
"ExecSpace": 6,
|
||||||
"Attributes": [],
|
"Attributes": [],
|
||||||
|
|||||||
60
RootDesk/MyDesk/PlayerLock.codeblock
Normal file
60
RootDesk/MyDesk/PlayerLock.codeblock
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "bdadf19a-cc27-4a45-99c6-7a439c858a1b",
|
"id": "bdadf19a-cc27-4a45-99c6-7a439c858a1b",
|
||||||
"path": "/maps/map01",
|
"path": "/maps/map01",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map01",
|
"name": "map01",
|
||||||
"path": "/maps/map01",
|
"path": "/maps/map01",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "000007d0-0000-4000-8000-0000000007d0",
|
"id": "000007d0-0000-4000-8000-0000000007d0",
|
||||||
"path": "/maps/map02",
|
"path": "/maps/map02",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map02",
|
"name": "map02",
|
||||||
"path": "/maps/map02",
|
"path": "/maps/map02",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00000bb8-0000-4000-8000-000000000bb8",
|
"id": "00000bb8-0000-4000-8000-000000000bb8",
|
||||||
"path": "/maps/map03",
|
"path": "/maps/map03",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map03",
|
"name": "map03",
|
||||||
"path": "/maps/map03",
|
"path": "/maps/map03",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00000fa0-0000-4000-8000-000000000fa0",
|
"id": "00000fa0-0000-4000-8000-000000000fa0",
|
||||||
"path": "/maps/map04",
|
"path": "/maps/map04",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map04",
|
"name": "map04",
|
||||||
"path": "/maps/map04",
|
"path": "/maps/map04",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00001388-0000-4000-8000-000000001388",
|
"id": "00001388-0000-4000-8000-000000001388",
|
||||||
"path": "/maps/map05",
|
"path": "/maps/map05",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map05",
|
"name": "map05",
|
||||||
"path": "/maps/map05",
|
"path": "/maps/map05",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00001770-0000-4000-8000-000000001770",
|
"id": "00001770-0000-4000-8000-000000001770",
|
||||||
"path": "/maps/map06",
|
"path": "/maps/map06",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map06",
|
"name": "map06",
|
||||||
"path": "/maps/map06",
|
"path": "/maps/map06",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00001b58-0000-4000-8000-000000001b58",
|
"id": "00001b58-0000-4000-8000-000000001b58",
|
||||||
"path": "/maps/map07",
|
"path": "/maps/map07",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map07",
|
"name": "map07",
|
||||||
"path": "/maps/map07",
|
"path": "/maps/map07",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00001f40-0000-4000-8000-000000001f40",
|
"id": "00001f40-0000-4000-8000-000000001f40",
|
||||||
"path": "/maps/map08",
|
"path": "/maps/map08",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map08",
|
"name": "map08",
|
||||||
"path": "/maps/map08",
|
"path": "/maps/map08",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00002328-0000-4000-8000-000000002328",
|
"id": "00002328-0000-4000-8000-000000002328",
|
||||||
"path": "/maps/map09",
|
"path": "/maps/map09",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map09",
|
"name": "map09",
|
||||||
"path": "/maps/map09",
|
"path": "/maps/map09",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00002710-0000-4000-8000-000000002710",
|
"id": "00002710-0000-4000-8000-000000002710",
|
||||||
"path": "/maps/map10",
|
"path": "/maps/map10",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map10",
|
"name": "map10",
|
||||||
"path": "/maps/map10",
|
"path": "/maps/map10",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
"id": "00002af8-0000-4000-8000-000000002af8",
|
"id": "00002af8-0000-4000-8000-000000002af8",
|
||||||
"path": "/maps/map11",
|
"path": "/maps/map11",
|
||||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera,script.PlayerLock",
|
||||||
"jsonString": {
|
"jsonString": {
|
||||||
"name": "map11",
|
"name": "map11",
|
||||||
"path": "/maps/map11",
|
"path": "/maps/map11",
|
||||||
@@ -1107,6 +1107,10 @@
|
|||||||
{
|
{
|
||||||
"@type": "script.MapCamera",
|
"@type": "script.MapCamera",
|
||||||
"Enable": true
|
"Enable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "script.PlayerLock",
|
||||||
|
"Enable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@version": 1
|
"@version": 1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// AI 전투 밸런스 시뮬레이터 — 오프라인 몬테카를로.
|
// AI 전투 밸런스 시뮬레이터 — 오프라인 몬테카를로.
|
||||||
// ⚠️ 전투 규칙은 tools/gen-slaydeck.mjs 의 Lua(SlayDeckController)와 동기화 유지할 것.
|
// ⚠️ 전투 규칙은 tools/deck/gen-slaydeck.mjs 의 Lua(SlayDeckController)와 동기화 유지할 것.
|
||||||
// (데이터는 data/*.json 공유, 규칙 로직은 JS로 중복 재현)
|
// (데이터는 data/*.json 공유, 규칙 로직은 JS로 중복 재현)
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
|||||||
|
|
||||||
// 맵별 고정 카메라: 맵 로드 시 플레이어 CameraComponent를 data/camera.json 값으로 설정.
|
// 맵별 고정 카메라: 맵 로드 시 플레이어 CameraComponent를 data/camera.json 값으로 설정.
|
||||||
// 새 CameraComponent를 만들지 않고(엔진 소유) 기존 카메라 속성만 런타임 설정한다.
|
// 새 CameraComponent를 만들지 않고(엔진 소유) 기존 카메라 속성만 런타임 설정한다.
|
||||||
|
// 플레이어 입력 차단·시선 고정은 tools/player/gen-player-lock.mjs(script.PlayerLock)로 분리됨.
|
||||||
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
||||||
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
||||||
|
|
||||||
@@ -65,16 +66,7 @@ local function apply()
|
|||||||
cam.ConfineCameraArea = ${CAM.confineCameraArea}
|
cam.ConfineCameraArea = ${CAM.confineCameraArea}
|
||||||
cam.CameraOffset = Vector2(${CAM.cameraOffsetX}, ${CAM.cameraOffsetY})
|
cam.CameraOffset = Vector2(${CAM.cameraOffsetX}, ${CAM.cameraOffsetY})
|
||||||
end
|
end
|
||||||
local pc = nil
|
if cam ~= nil then
|
||||||
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
|
|
||||||
_TimerService:ClearTimer(eventId)
|
_TimerService:ClearTimer(eventId)
|
||||||
elseif self.CamTries > 30 then
|
elseif self.CamTries > 30 then
|
||||||
_TimerService:ClearTimer(eventId)
|
_TimerService:ClearTimer(eventId)
|
||||||
@@ -133,7 +133,7 @@ function buildMap(nn) {
|
|||||||
return `map${tag}`;
|
return `map${tag}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 인자: 생성할 맵 번호(미지정 시 전체). 예: node tools/gen-maps.mjs 2
|
// 인자: 생성할 맵 번호(미지정 시 전체). 예: node tools/map/gen-maps.mjs 2
|
||||||
const arg = process.argv[2];
|
const arg = process.argv[2];
|
||||||
const targets = arg ? [Number(arg)] : MAP_NUMBERS;
|
const targets = arg ? [Number(arg)] : MAP_NUMBERS;
|
||||||
const made = targets.map(buildMap);
|
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