23 Commits

Author SHA1 Message Date
f89cd1ad63 Merge pull request '메이플 전사 전직 스킬 카드 및 연계 기믹 구현' (#110) from codex/maple-warrior-skill-cards into main
Reviewed-on: #110
2026-07-04 02:29:12 +09:00
758ebd19f2 balance: 전사 계열 5섹션 성장 곡선 조정 2026-07-04 02:22:17 +09:00
90494232bc feat: 메이플 전사 전직 카드와 연계 기믹 추가 2026-07-04 02:15:01 +09:00
ecadf3606e Merge pull request '워리어 카드 풀 및 공용 전투 효과 구현' (#109) from codex/warrior-card-effects-fixes into main
Reviewed-on: #109
2026-07-04 01:40:00 +09:00
4b559ca7fa feat: 워리어 카드와 공용 전투 효과 구현 2026-07-03 23:07:41 +09:00
30c73c9899 Merge pull request 'feat(monster): 적 종별 모델(프리팹) + 스테이지 로스터 배치' (#107) from feature/monster-models into main 2026-07-03 01:34:26 +09:00
ffcec649fa chore: 산출물 재생성 — map01~05 몬스터 인스턴스 entry_id 수정 반영
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-03 01:18:06 +09:00
3b678e35d1 fix(monster): 맵 인스턴스 origin.entry_id를 modelId와 일치시켜 MissingModel 해소
메이커가 엔티티의 원본 모델을 origin.entry_id로 해석하는데, entry_id를
enemyId("junior_bugi")로 넣어 model://monster-junior_bugi(실제 EntryKey)와
불일치 → [LEA-3028] MissingModel로 전 몬스터 미표시. entry_id를
modelEntryId(=modelId="monster-<id>")로 수정. 메이커 인게임 검증: 전투에서
전 종 정상 렌더(stump=1110101도 나무토막 확인).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-03 01:18:06 +09:00
47e954266c Merge pull request 'feat: 도적 밸런스 정리 및 전사 승급 계보 추가' (#108) from codex/rogue-campaign-balance into main
Reviewed-on: #108
2026-07-03 00:13:13 +09:00
8b4ece766a feat(warrior): add warrior promotion lineage 2026-07-02 23:47:01 +09:00
ee68fb5bb0 chore: 산출물 재생성 — stump 모델 + map04 배치 (18/18·전 5맵 완성)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 20:24:39 +09:00
a7f949fad8 data: stump(나무토막) 외형 추가 (mob 1110101) — 18/18 완성
MSW "나무토막" localized 검색에 잡힌 유일한 저레벨 몹 클립셋(1110101:
stand/hit/die/move) 채택. 시각 확정은 플레이테스트(map04)에서.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 20:24:39 +09:00
9fbf8e8574 chore: 산출물 재생성 — 모델 17종 + map02/03/05 로스터 배치
적 외형 8종 추가로 모델 8개 신규(slime·pig·mushmom·blue_mushroom 등),
map02/03/05가 준비도 가드 통과해 종별 모델 인스턴스로 재배치.
map04는 stump 미보유로 보존(스킵).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 19:57:40 +09:00
d61628d359 data: 적 외형 8종 추가 (공식 리소스 RUID 수확) — stump 제외 17/18
MSW 자산 검색으로 공식 maplestory 몹 클립 확보:
- slime(0210100)·slime_elite/boss(슬라임 재사용, box 스케일)
- pig(1210100)·mushmom(6130101)·blue_mushroom(2220110)
- red_snail·modified_snail(snail 0100100 — 빨강변형 0100102는 카탈로그 부재)
stump(나무토막)는 카탈로그에서 미발견 → 미보유 유지(map04 준비도 가드 스킵).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 19:57:40 +09:00
18da7a7983 docs: 몬스터 종별 모델 산출물 규칙(RULES §1)·README 반영
RULES §1 표에 Models/Monsters/<enemyId>.model 행 + 보조 생성기 3종 갱신.
README 디렉토리 구조·기능 표(몬스터 종별 모델)·재생성 명령 반영.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 13:57:22 +09:00
695f048c2d chore(map): 산출물 재생성 — map01 로스터 기반 몬스터 재배치(종별 모델 9마리)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 13:55:46 +09:00
6e82d0f128 feat(map): 인카운터를 encounters.json 로스터 기반 모델 인스턴스 배치로 개편
MONSTER_VARIANTS 랜덤 외형 제거(외형=enemies.json appearance로 정체성 고정).
buildMonsterInstance로 종별 모델(monster-<id>) 인스턴스 배치, 준비도 가드로
appearance 미보유 로스터 맵은 보존(Task 2 RUID 수확 후 재생성).
gen-combat-monster는 codeblock 생성만(맵 부착은 encounters 생성기로 흡수).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 13:55:46 +09:00
1390b9ec50 feat(monster): 적 종별 모델 생성기 + 모델 산출물 재생성(9종)
단일 소스 data/enemies.json appearance → Models/Monsters/<enemyId>.model.
EntryKey model://monster-<id> 네임스페이스(기존 모델과 충돌 가드), 태생 AI-free.
appearance 미확보 9종(slime 등)은 Task 2(RUID 수확) 후 추가 예정.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 13:52:45 +09:00
cdfc79cd57 feat(monster): 몬스터 모델·인스턴스 공용 빌더 lib + 단위테스트
buildMonsterModel(.model 골격 복제·외형/EnemyId 베이크·AI-free) +
buildMonsterInstance(맵 엔티티) + modelEntryId. fs 접근 없는 순수 함수.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 13:51:58 +09:00
bfb9ee5bef data: 적 외형(appearance) 9종 + 맵별 로스터(encounters.json) 신설
map01 메이커 큐레이션 외형을 enemies.json으로 흡수(단일 소스화).
encounters.json: map01=현 실태, map02~05=티어별 초안(기존 18종 내).
appearance는 luaEnemiesTable 화이트리스트 밖이라 codeblock 불변(diffcheck IDENTICAL).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xhLoQbJvQYL65kBtDNDTy
2026-07-02 13:50:42 +09:00
5297922f99 Merge pull request 'feat: 도적 직업 카드 및 5섹션 진행 밸런스 조정' (#106) from codex/rogue-campaign-balance into main
Reviewed-on: #106
2026-07-01 22:56:17 +09:00
e3a75c33a3 feat(rogue): localize custom card names 2026-07-01 22:53:54 +09:00
0a040837d9 feat(rogue): balance cards and campaign progression 2026-07-01 22:36:49 +09:00
57 changed files with 12218 additions and 4024 deletions

View File

@@ -45,7 +45,8 @@ git pull
slaymaple/
├── data/ # 게임 데이터 단일 소스 (생성기가 읽어 주입). 맵은 정적 데이터 없음(절차 생성)
│ ├── cards.json # 카드 166장(1~3차 전직 계열별 + 저주) + 클래스별 시작 덱
│ ├── enemies.json # 적 18종(일반/정예/보스, 디버프 인텐트 포함)
│ ├── enemies.json # 적 18종(일반/정예/보스, 디버프 인텐트 + 외형 appearance)
│ ├── encounters.json # 맵별 몬스터 로스터(map01~05 × combat/elite/boss)
│ ├── potions.json # 물약 6종 + 드랍률·슬롯·상점가
│ ├── relics.json # 유물 19종(StS 효과 × 메이플 장비) + 시작 유물 + 풀
│ ├── cardframes.json # 커스텀 카드 프레임 3종(전사/마법사/도적 × normal/unique/legend) + 보상 등급 가중치
@@ -68,16 +69,17 @@ slaymaple/
│ ├── MapCamera.codeblock # 맵별 카메라 적용
│ ├── PlayerLock.codeblock # 전투맵 플레이어 입력·이동 잠금
│ ├── LobbyNpc.codeblock # 로비 NPC 상호작용(근접·클릭)
── LobbyMobility.codeblock # 로비 이동·공격 해제 + 카메라 추종
── LobbyMobility.codeblock # 로비 이동·공격 해제 + 카메라 추종
│ └── Models/Monsters/ # 적 종별 모델 <enemyId>.model (산출물 — 외형·EnemyId 베이크)
├── map/ # 맵 6종 (산출물)
│ ├── lobby.map # 로비 허브 맵 (마을 배경, NPC 4종, 전투 없음)
│ └── map01.map ~ map05.map # 5막 전투/맵 노드 (공식 배경 + STS풍 우측 배치)
├── tools/ # 결정적 생성기·도구 (주체별 폴더, 단일 소스)
│ ├── deck/ # gen-slaydeck.mjs(★컨트롤러+common 생성 오케스트레이터) · cb/(codeblock Lua 메서드 20모듈: boot·screens·combat·hand·npc·navigation·layout·shop·reward·soul 등) · lib/(공유 상수·데이터·헬퍼) · legacy/(옛 UI emit 휴면)
│ ├── map/ # gen-maps.mjs(맵 배경/타일) · gen-lobby-map.mjs(로비 맵+NPC) · gen-map-encounters.mjs(노드별 몬스터 그룹) · rogue-map.mjs(절차 생성 JS 미러)+test
│ ├── map/ # gen-maps.mjs(맵 배경/타일) · gen-lobby-map.mjs(로비 맵+NPC) · gen-map-encounters.mjs(encounters.json 로스터 기반 종별 모델 인스턴스 배치) · rogue-map.mjs(절차 생성 JS 미러)+test
│ ├── camera/ # gen-camera.mjs(맵별 고정 카메라 codeblock)
│ ├── player/ # gen-player-lock.mjs(전투맵 입력 잠금) · freeze-turn-player.mjs(모델 이동 정지) · gen-lobby-npc.mjs(LobbyNpc·LobbyMobility codeblock)
│ ├── monster/ # gen-combat-monster.mjs(EnemyId 마커) · freeze-turn-monsters.mjs(필드 AI 정지)
│ ├── monster/ # gen-monster-models.mjs(적 종별 .model) · gen-combat-monster.mjs(자기등록 codeblock) · freeze-turn-monsters.mjs(레거시 AI 정지) · lib/monster-model.mjs(공용 빌더)+test
│ ├── balance/ # sim-balance.mjs(전투 밸런스 몬테카를로 시뮬) · sim-balance.test.mjs
│ ├── verify/ # count·uimap·cbgap(카운트/UIGroup 매핑/재연결 GAP) · cardkinds(카드 kind↔효과) · cbprops(미선언 self 대입) · cbset(메서드 집합 무손실) · diffcheck(바이트동일)
│ └── git/ # gitea-pr.mjs(UTF-8 안전 PR 생성/수정/머지 — RULES.md 참조)
@@ -127,6 +129,7 @@ slaymaple/
| **버프/디버프** | StS 표준 — **힘**(+N 영구)·**약화**(주는 피해 25%)·**취약**(받는 피해 +50%)·**독**(매 행동 틱). 양방향(적 디버프 인텐트 포함), 인텐트는 최종 예상치 표시 |
| **전투 연출** | 공격 이펙트·**몬스터 데미지 팝업(자릿수 스킨)**·드래그 타깃 마커·적 개별 차례·**공격/피격/독뎀 모션**(아바타 상태 전이·몬스터 hit 클립·런지/넉백) |
| **절차 생성 맵** | 막 시작마다 **경로 생성**(런마다 다름, **가로 진행**). 층 규칙: 1~2층 전투만 → 3층~ 상점/휴식 → 4층~ 엘리트/**유물 방** → 보스 수렴. 점선 경로·상태 4단·층 카운터. 노드 타입별 **몬스터 랜덤 구성**(일반 1~3 / 엘리트 / 보스) + intent 랜덤 행동 |
| **몬스터 종별 모델** | 적 종별 전용 `.model`(프리팹) — 외형(stand/hit/die)·EnemyId 베이크, 태생 AI-free. 맵 배치는 **`data/encounters.json` 맵별 로스터**대로 해당 모델 인스턴스 생성(외형=정체성 고정). 능력치·행동은 `enemies.json`, 외형은 `appearance`, 배치는 `encounters.json`로 관심사 분리 |
| **유물 19종 / 물약 6종** | 유물: StS 효과 × 메이플 장비 외형, TopBar 아이콘 + 마우스오버 툴팁, 8종 훅. 물약: 승리 40% 드랍·상점·슬롯 메뉴. 보물 방=상자 연출 → 유물+메소 |
| **카드 프레임·등급** | 커스텀 프레임 3종(전사/마법사/도적 × normal/unique/legend), 카드 5개 사이트 통합 레이아웃. 보상 등급 가중 추첨 70/25/5 |
| **영혼(Soul) 메타 성장** | 승천과 별개의 영구 강화 화폐. 2차 전직 상태로 보스 클리어 시 적립 → 로비 영혼 상점 4종 해금(시작 메소 +60·HP +15·덱 정제·시작 유물 +1). **UserDataStorage 영구 저장** |
@@ -179,7 +182,9 @@ node tools/map/gen-lobby-map.mjs # 로비 맵 + NPC 배치
node tools/player/gen-lobby-npc.mjs # 로비 codeblock(LobbyNpc·LobbyMobility)
node tools/camera/gen-camera.mjs # 맵별 카메라
node tools/player/gen-player-lock.mjs # 전투맵 입력 잠금
node tools/monster/gen-combat-monster.mjs # 몬스터 EnemyId 마커
node tools/monster/gen-monster-models.mjs # 적 종별 모델 .model (외형=enemies.json appearance)
node tools/monster/gen-combat-monster.mjs # 자기등록 마커 codeblock
node tools/map/gen-map-encounters.mjs # encounters.json 로스터 기반 맵 몬스터 배치
```
> 산출물 검증은 내용 출력 없이 카운트만: `node tools/verify/count.mjs <ui|cb|common> <regex>...`. 정적 가드 — 카드 kind↔효과 `cardkinds.mjs` · 미선언 self 대입 `cbprops.mjs` · UI 경로 재연결 GAP `cbgap.mjs` · 리팩터 바이트동일 `diffcheck.mjs` (자세한 가드는 [`RULES.md`](RULES.md)).

View File

@@ -16,6 +16,7 @@ Claude Code는 `CLAUDE.md`가 이 파일을 임포트하므로 자동 적용된
| `Global/common.gamelogic` | ~1KB | 〃 | 〃 |
| `map/map01.map`~`map05.map`, `map/lobby.map` | 각 ~210KB | `tools/map/`·`tools/monster/`·`tools/camera/`·`tools/player/` (↓ 보조 생성기) | 해당 생성기 |
| `RootDesk/MyDesk/CombatMonster.codeblock` | ~2KB | `tools/monster/gen-combat-monster.mjs` | `node tools/monster/gen-combat-monster.mjs` |
| `RootDesk/MyDesk/Models/Monsters/<enemyId>.model` (적 종별, 최대 18종) | 각 ~5KB | `data/enemies.json`(`appearance`) + `tools/monster/gen-monster-models.mjs` | `node tools/monster/gen-monster-models.mjs` |
| `RootDesk/MyDesk/PlayerLock.codeblock` | ~2KB | `tools/player/gen-player-lock.mjs` | `node tools/player/gen-player-lock.mjs` |
| `RootDesk/MyDesk/MapCamera.codeblock` | ~2KB | `tools/camera/gen-camera.mjs` (값: `data/camera.json`) | `node tools/camera/gen-camera.mjs` |
| `RootDesk/MyDesk/LobbyNpc.codeblock`·`LobbyMobility.codeblock` | 각 ~2-3KB | `tools/player/gen-lobby-npc.mjs` | `node tools/player/gen-lobby-npc.mjs` |
@@ -31,9 +32,10 @@ Claude Code는 `CLAUDE.md`가 이 파일을 임포트하므로 자동 적용된
- `tools/camera/gen-camera.mjs``MapCamera.codeblock` + map01~05 카메라 부착 (값 `data/camera.json`)
- `tools/map/gen-maps.mjs``map02~05` + `Global/SectorConfig.config` (map01 템플릿 클론)
- `tools/map/gen-lobby-map.mjs``map/lobby.map` + `SectorConfig.config`
- `tools/map/gen-map-encounters.mjs` → map01~05 노드 타입별 몬스터 그룹 재구성
- `tools/monster/gen-combat-monster.mjs``CombatMonster.codeblock` + map01~05 부착
- `tools/monster/freeze-turn-monsters.mjs`몬스터 `.model`·맵 AI 컴포넌트 제거
- `tools/monster/gen-monster-models.mjs``Models/Monsters/<enemyId>.model`(적 종별, 외형·EnemyId 베이크·태생 AI-free). 단일 소스 `data/enemies.json``appearance`. `appearance` 미보유 적은 스킵(메이커 저작 모델 `HolodragonKing`·`Model_monster-43`은 예외 — 이 생성기 대상 아님). 공용 빌더 `tools/monster/lib/monster-model.mjs`.
- `tools/map/gen-map-encounters.mjs`map01~05에 `data/encounters.json` 로스터 기반 **종별 모델 인스턴스** 배치(외형=정체성 고정). 준비도 가드: 로스터에 `appearance` 미보유 적이 있는 맵은 재생성 스킵(기존 보존). 값 검증 `node --test tools/monster/monster-model.test.mjs`.
- `tools/monster/gen-combat-monster.mjs``CombatMonster.codeblock`(자기등록 마커)만 생성. 맵 부착값(EnemyId/Group)은 `gen-map-encounters.mjs`가 인스턴스에 직접 기록.
- `tools/monster/freeze-turn-monsters.mjs` → 레거시 공용 몬스터 `.model`·맵 AI 컴포넌트 제거(신규 종별 모델은 태생 frozen — 대상 아님).
- `tools/player/gen-player-lock.mjs``PlayerLock.codeblock` + map01~05 부착
- `tools/player/gen-lobby-npc.mjs``LobbyNpc.codeblock`·`LobbyMobility.codeblock`
- `tools/player/freeze-turn-player.mjs``Global/DefaultPlayer.model` 이동 0 고정

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-blue_mushroom",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "blue_mushroom",
"BaseModelId": null,
"Id": "monster-blue_mushroom",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "1a176a7afb114fe7aef2bc58ef2d945b"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "1a176a7afb114fe7aef2bc58ef2d945b",
"move": "8239541953a6457fbe6d35e17f19f0f8",
"hit": "7b405108d05741699893a4dc3d715165",
"jump": "a7ea0755262242199ae50ab6a3387034",
"die": "9e74e807797d442f9c938ca64aa9f4cd"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "blue_mushroom"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-dile",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "dile",
"BaseModelId": null,
"Id": "monster-dile",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "68070c6f4abe40658899a208ddaf4081"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"move": "426ba2c6fa2d4cdd92bcb0bb37861dcc",
"stand": "68070c6f4abe40658899a208ddaf4081",
"skill": "4ba2cdc2f11746afa0f542293b0618d5",
"hit": "172640e6d4ce444aa1dfbd9bd9523eb1",
"die": "5d50d9aa34c745b9b8932c15da919927"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 2.2,
"y": 1.51
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": -0.220000029,
"y": 0.755
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "dile"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,229 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-green_mushroom",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "green_mushroom",
"BaseModelId": null,
"Id": "monster-green_mushroom",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "f86992ba9c41487c8480fcb893fcbda6"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "f86992ba9c41487c8480fcb893fcbda6",
"hit": "d305b942b1704c8084548108ff3b7a6b",
"die": "5a563e5fd98c4132b61057dc6bb8aaf2"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.00999999,
"y": 0.26
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "green_mushroom"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,229 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-junior_bugi",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "junior_bugi",
"BaseModelId": null,
"Id": "monster-junior_bugi",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "a2204a21d88942b281d2cac6053ffbaa"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "a2204a21d88942b281d2cac6053ffbaa",
"hit": "afc08936b8a64b26bc3dd8c03ead1f26",
"die": "fc1c6d9ba9bc413ab53b6dbfae3ac45b"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "junior_bugi"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,229 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-junior_neki",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "junior_neki",
"BaseModelId": null,
"Id": "monster-junior_neki",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "48c10437ae8344a9b2a1d3f36185728f"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "48c10437ae8344a9b2a1d3f36185728f",
"hit": "9044063647854f5e9128efcf80e909be",
"die": "f414577d18c94cc387c275df4abdbc3b"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "junior_neki"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,229 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-kapa_drake",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "kapa_drake",
"BaseModelId": null,
"Id": "monster-kapa_drake",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "4ca39dbfa1c6492283ba8bd352d12b0a"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "4ca39dbfa1c6492283ba8bd352d12b0a",
"hit": "7ac78511036e4ebe988b97c35fc275d1",
"die": "740f3f2b2e7a4b71bec5eac84e8539f9"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "kapa_drake"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,233 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-king_slime",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "king_slime",
"BaseModelId": null,
"Id": "monster-king_slime",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "dd9de73d580240faab8cad03b587013b"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"move": "873425127b75475b9944dc86bf77f885",
"stand": "dd9de73d580240faab8cad03b587013b",
"jump": "6a2b983b7a31417ca19c29c3d1d00817",
"attack": "a34d1146057443fd8b578dafeb7c2ed1",
"skill": "0b0bb78f0ca44526bad6d994bb16f973",
"hit": "d2de42d3233b42a58d9799d5e762a19c",
"die": "5bd3969c3bcb4df2bd79c2b940ee03dc"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 2.19,
"y": 1.39
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.335000038,
"y": 0.695
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "king_slime"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-mano",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "mano",
"BaseModelId": null,
"Id": "monster-mano",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "e035bb90c053401b88de2159dfa230eb"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"move": "3dcd0dc63d2d491b9b8d39b3b9d0a214",
"stand": "e035bb90c053401b88de2159dfa230eb",
"skill": "c05453dd21fd4ed581d193930ab4c331",
"hit": "452cb740ddcb4837a46b75d7935e2ffc",
"die": "f430051f6fc34f2eb56fe5e62b346eac"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 1.05,
"y": 0.95
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.004999995,
"y": 0.475
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "mano"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,230 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-modified_snail",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "modified_snail",
"BaseModelId": null,
"Id": "monster-modified_snail",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "17b55730c26f4fd6b8fcfa288da388de"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "17b55730c26f4fd6b8fcfa288da388de",
"move": "f40108c8b0b84696a67337b801201f7d",
"hit": "eac48e84a9fc4580a4018de5cf52ddb3",
"die": "51c2f4b59a2c413db26035aa57002fc8"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.75,
"y": 0.68
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "modified_snail"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,232 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-mushmom",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "mushmom",
"BaseModelId": null,
"Id": "monster-mushmom",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "23c38ef3acad4a30ad59120bb939b008"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "23c38ef3acad4a30ad59120bb939b008",
"move": "24d8a3a75f96406ba690ed42d7250b8f",
"hit": "c826e36ee89c48bca6aab856aa773f38",
"attack": "4d7465e950144dc59c263aad01b14e14",
"jump": "b7ddbda71a294141ba134249fc34c7da",
"die": "f50664a4524147399359cb90a6f3e80c"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 1.2,
"y": 1.1
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.02,
"y": 0.55
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "mushmom"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,229 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-octopus",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "octopus",
"BaseModelId": null,
"Id": "monster-octopus",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "d8f014043ce8418f96700c2b6c9ebf6c"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "d8f014043ce8418f96700c2b6c9ebf6c",
"hit": "c3cf643b618346c7bfa6574187b396f9",
"die": "a88d9b3d60f941e4890dc89a6ccaa8ee"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "octopus"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-orange_mushroom",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "orange_mushroom",
"BaseModelId": null,
"Id": "monster-orange_mushroom",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "6d381bea1bcb4504b518a1fbfa0904ac"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"move": "573fe938562a4abf91eebf951f21afd5",
"stand": "6d381bea1bcb4504b518a1fbfa0904ac",
"jump": "59823e146a034e48b8667ebb6f0724b1",
"hit": "642ece38d8d449b29ce4479100e37a54",
"die": "3c99d6b9b89b4295a9c2749eb02e28e9"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "orange_mushroom"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-pig",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "pig",
"BaseModelId": null,
"Id": "monster-pig",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "528a8638b12f41b8b5781a05360d2949"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "528a8638b12f41b8b5781a05360d2949",
"move": "8baad61512be4b33b2a0879fec7a266e",
"hit": "60e42a918a0342478903cc71adba1dc5",
"jump": "c9e27ce6f8344aefba169c5ca6571def",
"die": "0644beff80a44ec7acc011ea0961df57"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "pig"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,230 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-red_snail",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "red_snail",
"BaseModelId": null,
"Id": "monster-red_snail",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "17b55730c26f4fd6b8fcfa288da388de"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "17b55730c26f4fd6b8fcfa288da388de",
"move": "f40108c8b0b84696a67337b801201f7d",
"hit": "eac48e84a9fc4580a4018de5cf52ddb3",
"die": "51c2f4b59a2c413db26035aa57002fc8"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "red_snail"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-slime",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "slime",
"BaseModelId": null,
"Id": "monster-slime",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "50faf654ee5d479cb2958edce9feaef0"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "slime"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-slime_boss",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "slime_boss",
"BaseModelId": null,
"Id": "monster-slime_boss",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "50faf654ee5d479cb2958edce9feaef0"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 1.2,
"y": 1.1
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.4
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "slime_boss"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,231 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-slime_elite",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "slime_elite",
"BaseModelId": null,
"Id": "monster-slime_elite",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "50faf654ee5d479cb2958edce9feaef0"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.85,
"y": 0.78
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "slime_elite"
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -0,0 +1,230 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://monster-stump",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.3.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "stump",
"BaseModelId": null,
"Id": "monster-stump",
"Components": [
"MOD.Core.TransformComponent",
"MOD.Core.StateAnimationComponent",
"MOD.Core.SpriteRendererComponent",
"MOD.Core.RigidbodyComponent",
"MOD.Core.MovementComponent",
"MOD.Core.StateComponent",
"MOD.Core.HitComponent",
"MOD.Core.DamageSkinSpawnerComponent",
"script.Monster",
"script.MonsterAttack",
"MOD.Core.KinematicbodyComponent",
"MOD.Core.SideviewbodyComponent",
"MOD.Core.DamageSkinSettingComponent",
"script.CombatMonster"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "speed",
"DisplayName": "speed",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "InputSpeed"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "jumpForce",
"DisplayName": "jumpForce",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.MovementComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "JumpForce"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "actionSheet",
"DisplayName": "actionSheet",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.StateAnimationComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ActionSheet"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Name": "renderguid",
"DisplayName": "renderguid",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "SpriteRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.RenderSettingType, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "renderSetting",
"DisplayName": "renderSetting",
"ShowInInspector": false,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteRendererComponent, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RenderSetting"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "OrderInLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 2
},
{
"TargetType": null,
"Name": "renderguid",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "null"
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "CollisionGroup",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.Physics.CollisionGroup, MOD.Core, Version=26.3.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.Physics.CollisionGroup, MOD.Core",
"Id": "8992acd1e8cd45838db6f10a7b41df09"
}
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SpriteRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "ed3908e24d694bb786023fc1ed073489"
},
{
"TargetType": "MOD.Core.SpriteRendererComponent",
"Name": "SortingLayer",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "MapLayer0"
},
{
"TargetType": "MOD.Core.StateAnimationComponent",
"Name": "ActionSheet",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"stand": "ed3908e24d694bb786023fc1ed073489",
"move": "9a4cad470f304753885e06c043156efb",
"hit": "4763c9bebc9245998c9c499b6316aa9f",
"die": "b168793b92a844a3a3a6f4ce647a14d2"
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "BoxSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.63,
"y": 0.58
}
},
{
"TargetType": "MOD.Core.HitComponent",
"Name": "ColliderOffset",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0449999869,
"y": 0.29
}
},
{
"TargetType": "MOD.Core.MovementComponent",
"Name": "InputSpeed",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": 0
},
{
"TargetType": "script.CombatMonster",
"Name": "EnemyId",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "stump"
}
],
"EventLinks": [],
"Children": []
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -19,7 +19,9 @@
"classToFrame": {
"warrior": "warrior",
"fighter": "warrior",
"crusader": "warrior",
"page": "warrior",
"knight": "warrior",
"spearman": "warrior",
"magician": "magician",
"firepoison": "magician",

File diff suppressed because it is too large Load Diff

Binary file not shown.

7
data/encounters.json Normal file
View File

@@ -0,0 +1,7 @@
{
"map01": { "combat": ["junior_bugi", "kapa_drake", "junior_neki", "octopus", "green_mushroom", "orange_mushroom"], "elite": ["dile", "mano"], "boss": ["king_slime"] },
"map02": { "combat": ["pig", "green_mushroom", "blue_mushroom", "orange_mushroom", "slime"], "elite": ["mushmom"], "boss": ["slime_boss"] },
"map03": { "combat": ["octopus", "junior_neki", "junior_bugi", "slime"], "elite": ["slime_elite"], "boss": ["king_slime"] },
"map04": { "combat": ["kapa_drake", "junior_neki", "junior_bugi", "stump"], "elite": ["dile"], "boss": ["mushmom"] },
"map05": { "combat": ["kapa_drake", "octopus", "junior_bugi", "junior_neki"], "elite": ["dile", "slime_elite"], "boss": ["king_slime"] }
}

View File

@@ -7,7 +7,8 @@
{ "kind": "Attack", "value": 10 },
{ "kind": "Attack", "value": 6 },
{ "kind": "Defend", "value": 8 }
]
],
"appearance": { "sheet": { "stand": "50faf654ee5d479cb2958edce9feaef0", "move": "dc932872543f4a02bf41e977ab79e5ad", "hit": "61c27025a8f14c478f30ede1b49758bc", "jump": "8b89d86b1a9c4c4288650614c6f30e67", "die": "31ecb6c7cbc24599881f00cb01599f09" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"slime_elite": {
"name": "정예 슬라임",
@@ -17,7 +18,8 @@
{ "kind": "Attack", "value": 8 },
{ "kind": "Defend", "value": 10 },
{ "kind": "Debuff", "effect": "weak", "value": 1 }
]
],
"appearance": { "sheet": { "stand": "50faf654ee5d479cb2958edce9feaef0", "move": "dc932872543f4a02bf41e977ab79e5ad", "hit": "61c27025a8f14c478f30ede1b49758bc", "jump": "8b89d86b1a9c4c4288650614c6f30e67", "die": "31ecb6c7cbc24599881f00cb01599f09" }, "box": { "x": 0.85, "y": 0.78 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"slime_boss": {
"name": "슬라임 킹",
@@ -28,7 +30,8 @@
{ "kind": "Debuff", "effect": "vuln", "value": 2 },
{ "kind": "Attack", "value": 10 },
{ "kind": "Attack", "value": 22 }
]
],
"appearance": { "sheet": { "stand": "50faf654ee5d479cb2958edce9feaef0", "move": "dc932872543f4a02bf41e977ab79e5ad", "hit": "61c27025a8f14c478f30ede1b49758bc", "jump": "8b89d86b1a9c4c4288650614c6f30e67", "die": "31ecb6c7cbc24599881f00cb01599f09" }, "box": { "x": 1.2, "y": 1.1 }, "off": { "x": 0.0449999869, "y": 0.4 } }
},
"orange_mushroom": {
"name": "주황버섯",
@@ -38,7 +41,8 @@
{ "kind": "Attack", "value": 5 },
{ "kind": "Defend", "value": 4 },
{ "kind": "Attack", "value": 8 }
]
],
"appearance": { "sheet": { "move": "573fe938562a4abf91eebf951f21afd5", "stand": "6d381bea1bcb4504b518a1fbfa0904ac", "jump": "59823e146a034e48b8667ebb6f0724b1", "hit": "642ece38d8d449b29ce4479100e37a54", "die": "3c99d6b9b89b4295a9c2749eb02e28e9" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"blue_mushroom": {
"name": "파란버섯",
@@ -48,7 +52,8 @@
{ "kind": "Attack", "value": 4 },
{ "kind": "Attack", "value": 10 },
{ "kind": "AddCard", "card": "Wound", "count": 1 }
]
],
"appearance": { "sheet": { "stand": "1a176a7afb114fe7aef2bc58ef2d945b", "move": "8239541953a6457fbe6d35e17f19f0f8", "hit": "7b405108d05741699893a4dc3d715165", "jump": "a7ea0755262242199ae50ab6a3387034", "die": "9e74e807797d442f9c938ca64aa9f4cd" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"pig": {
"name": "돼지",
@@ -57,7 +62,8 @@
{ "kind": "Attack", "value": 6 },
{ "kind": "Attack", "value": 6 },
{ "kind": "Defend", "value": 5 }
]
],
"appearance": { "sheet": { "stand": "528a8638b12f41b8b5781a05360d2949", "move": "8baad61512be4b33b2a0879fec7a266e", "hit": "60e42a918a0342478903cc71adba1dc5", "jump": "c9e27ce6f8344aefba169c5ca6571def", "die": "0644beff80a44ec7acc011ea0961df57" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"green_mushroom": {
"name": "초록버섯",
@@ -66,7 +72,8 @@
{ "kind": "Attack", "value": 7 },
{ "kind": "Defend", "value": 3 },
{ "kind": "Attack", "value": 9 }
]
],
"appearance": { "sheet": { "stand": "f86992ba9c41487c8480fcb893fcbda6", "hit": "d305b942b1704c8084548108ff3b7a6b", "die": "5a563e5fd98c4132b61057dc6bb8aaf2" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.00999999, "y": 0.26 } }
},
"red_snail": {
"name": "빨간 달팽이",
@@ -75,7 +82,8 @@
{ "kind": "Attack", "value": 5 },
{ "kind": "Defend", "value": 6 },
{ "kind": "Attack", "value": 7 }
]
],
"appearance": { "sheet": { "stand": "17b55730c26f4fd6b8fcfa288da388de", "move": "f40108c8b0b84696a67337b801201f7d", "hit": "eac48e84a9fc4580a4018de5cf52ddb3", "die": "51c2f4b59a2c413db26035aa57002fc8" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"stump": {
"name": "나무토막",
@@ -84,7 +92,8 @@
{ "kind": "Defend", "value": 5 },
{ "kind": "Attack", "value": 8 },
{ "kind": "Attack", "value": 6 }
]
],
"appearance": { "sheet": { "stand": "ed3908e24d694bb786023fc1ed073489", "move": "9a4cad470f304753885e06c043156efb", "hit": "4763c9bebc9245998c9c499b6316aa9f", "die": "b168793b92a844a3a3a6f4ce647a14d2" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"mushmom": {
"name": "머쉬맘",
@@ -96,7 +105,8 @@
{ "kind": "Attack", "value": 9 },
{ "kind": "Defend", "value": 6 },
{ "kind": "AddCard", "card": "Burn", "count": 1 }
]
],
"appearance": { "sheet": { "stand": "23c38ef3acad4a30ad59120bb939b008", "move": "24d8a3a75f96406ba690ed42d7250b8f", "hit": "c826e36ee89c48bca6aab856aa773f38", "attack": "4d7465e950144dc59c263aad01b14e14", "jump": "b7ddbda71a294141ba134249fc34c7da", "die": "f50664a4524147399359cb90a6f3e80c" }, "box": { "x": 1.2, "y": 1.1 }, "off": { "x": 0.02, "y": 0.55 } }
},
"modified_snail": {
"name": "변형된 달팽이",
@@ -107,7 +117,8 @@
{ "kind": "Attack", "value": 7 },
{ "kind": "Attack", "value": 14 },
{ "kind": "Debuff", "effect": "weak", "value": 1 }
]
],
"appearance": { "sheet": { "stand": "17b55730c26f4fd6b8fcfa288da388de", "move": "f40108c8b0b84696a67337b801201f7d", "hit": "eac48e84a9fc4580a4018de5cf52ddb3", "die": "51c2f4b59a2c413db26035aa57002fc8" }, "box": { "x": 0.75, "y": 0.68 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"king_slime": {
"name": "킹 슬라임",
@@ -118,7 +129,8 @@
{ "kind": "Debuff", "effect": "vuln", "value": 2 },
{ "kind": "Attack", "value": 12 },
{ "kind": "Attack", "value": 24 }
]
],
"appearance": { "sheet": { "move": "873425127b75475b9944dc86bf77f885", "stand": "dd9de73d580240faab8cad03b587013b", "jump": "6a2b983b7a31417ca19c29c3d1d00817", "attack": "a34d1146057443fd8b578dafeb7c2ed1", "skill": "0b0bb78f0ca44526bad6d994bb16f973", "hit": "d2de42d3233b42a58d9799d5e762a19c", "die": "5bd3969c3bcb4df2bd79c2b940ee03dc" }, "box": { "x": 2.19, "y": 1.39 }, "off": { "x": 0.335000038, "y": 0.695 } }
},
"octopus": {
"name": "문어",
@@ -127,7 +139,8 @@
{ "kind": "Attack", "value": 5 },
{ "kind": "Attack", "value": 6 },
{ "kind": "Defend", "value": 4 }
]
],
"appearance": { "sheet": { "stand": "d8f014043ce8418f96700c2b6c9ebf6c", "hit": "c3cf643b618346c7bfa6574187b396f9", "die": "a88d9b3d60f941e4890dc89a6ccaa8ee" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"kapa_drake": {
"name": "카파 드레이크",
@@ -137,7 +150,8 @@
{ "kind": "Attack", "value": 6 },
{ "kind": "Defend", "value": 6 },
{ "kind": "Attack", "value": 11 }
]
],
"appearance": { "sheet": { "stand": "4ca39dbfa1c6492283ba8bd352d12b0a", "hit": "7ac78511036e4ebe988b97c35fc275d1", "die": "740f3f2b2e7a4b71bec5eac84e8539f9" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"junior_neki": {
"name": "주니어 네키",
@@ -146,7 +160,8 @@
{ "kind": "Attack", "value": 6 },
{ "kind": "Attack", "value": 8 },
{ "kind": "Debuff", "effect": "weak", "value": 1 }
]
],
"appearance": { "sheet": { "stand": "48c10437ae8344a9b2a1d3f36185728f", "hit": "9044063647854f5e9128efcf80e909be", "die": "f414577d18c94cc387c275df4abdbc3b" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"junior_bugi": {
"name": "주니어 부기",
@@ -155,7 +170,8 @@
{ "kind": "Attack", "value": 7 },
{ "kind": "Defend", "value": 5 },
{ "kind": "Attack", "value": 9 }
]
],
"appearance": { "sheet": { "stand": "a2204a21d88942b281d2cac6053ffbaa", "hit": "afc08936b8a64b26bc3dd8c03ead1f26", "die": "fc1c6d9ba9bc413ab53b6dbfae3ac45b" }, "box": { "x": 0.63, "y": 0.58 }, "off": { "x": 0.0449999869, "y": 0.29 } }
},
"dile": {
"name": "다일",
@@ -166,7 +182,8 @@
{ "kind": "Attack", "value": 8 },
{ "kind": "Attack", "value": 16 },
{ "kind": "Debuff", "effect": "weak", "value": 1 }
]
],
"appearance": { "sheet": { "move": "426ba2c6fa2d4cdd92bcb0bb37861dcc", "stand": "68070c6f4abe40658899a208ddaf4081", "skill": "4ba2cdc2f11746afa0f542293b0618d5", "hit": "172640e6d4ce444aa1dfbd9bd9523eb1", "die": "5d50d9aa34c745b9b8932c15da919927" }, "box": { "x": 2.2, "y": 1.51 }, "off": { "x": -0.220000029, "y": 0.755 } }
},
"mano": {
"name": "마노",
@@ -177,7 +194,8 @@
{ "kind": "Debuff", "effect": "vuln", "value": 1 },
{ "kind": "Attack", "value": 10 },
{ "kind": "AddCard", "card": "Wound", "count": 1 }
]
],
"appearance": { "sheet": { "move": "3dcd0dc63d2d491b9b8d39b3b9d0a214", "stand": "e035bb90c053401b88de2159dfa230eb", "skill": "c05453dd21fd4ed581d193930ab4c331", "hit": "452cb740ddcb4837a46b75d7935e2ffc", "die": "f430051f6fc34f2eb56fe5e62b346eac" }, "box": { "x": 1.05, "y": 0.95 }, "off": { "x": 0.004999995, "y": 0.475 } }
}
},
"activeEnemy": "slime",

View File

@@ -1,10 +1,7 @@
# 공격 적중
# 공격
`attackPoison`은 전투 중 파워가 들고 있는 공용 필드입니다.
동작:
- 공격 카드가 실제 피해를 주면 독을 부여합니다.
- `aoe` 공격이면 모든 적에게 같은 양의 독을 붙입니다.
- `Envenom` 같은 카드가 이 필드를 사용합니다.
`attackPoison`은 전투 동안 유지되는 공용 카드 효과 필드입니다.
- 공격 카드가 실제 체력 피해를 주면 대상에게 지정된 수치만큼 중독을 부여합니다.
- 광역 공격은 피해를 받은 각 적에게 중독을 부여합니다.
- 현재 Thief Master와 Hermit의 `베놈`이 이 효과를 사용합니다.

View File

@@ -1,12 +1,34 @@
# Rogue Card Audit
# 도적 카드 구성 및 밸런스 기록
Current status of rogue cards and shared effect hooks.
도적 계보의 카드 역할, 직업 이동 금지 대상, 공용 효과 필드를 정리합니다.
## Implemented
## 직업별 컨셉
`Neutralize`, `SilentStrike`, `Survivor`, `SilentDefend`, `Slice`, `DaggerSpray`, `DaggerThrow`, `PoisonedStab`, `SuckerPunch`, `LeadingStrike`, `FollowThrough`, `FlickFlack`, `Prepared`, `Deflect`, `BladeDance`, `Backflip`, `DodgeAndRoll`, `CloakAndDagger`, `DeadlyPoison`, `Snakebite`, `Untouchable`, `Backstab`, `PreciseCut`, `Finisher`, `MementoMori`, `Flechettes`, `Dash`, `Predator`, `CalculatedGamble`, `HiddenDaggers`, `Acrobatics`, `Blur`, `LegSweep`, `Reflex`, `Haze`, `Tactician`, `WellLaidPlans`, `InfiniteBlades`, `Footwork`, `GrandFinale`, `Adrenaline`, `ShadowStep`, `Assassinate`, `Nightmare`, `ToolsOfTheTrade`, `Afterimage`, `Burst`, `StormOfSteel`, `Abrasive`, `Suppress`, `Expertise`, `Shadowmeld`, `Pounce`, `BouncingFlask`, `Accuracy`, `PhantomBlades`, `Speedster`, `CorrosiveWave`, `Tracking`, `FanOfKnives`, `Strangle`, `Mirage`, `Accelerant`, `MasterPlanner`, `Outbreak`, `EscapePlan`, `HandTrick`, `NoxiousFumes`, `Pinpoint`, `TheHunt`, `Murder`, `Malaise`, `BladeOfInk`, `KnifeTrap`, `BulletTime`, `Envenom`, `SerpentForm`, `WraithForm`, `Skewer`, `Ricochet`, `Anticipate`, `PiercingWail`, `Expose`, `UpMySleeve`, `EchoingSlash`, `BubbleBubble`
- `rogue`: 시작 카드, 1차 스킬, 기초 공격·회피·방어
- `thief`: 단검 난타, 교활, 버리기, 중독의 시작
- `thiefmaster`: 교활·버리기 연계 완성, 광역 난타, 중독 증폭
- `assassin`: 표창 생성, 표창 연속 공격, 표창 비용·피해 보조
- `hermit`: 표창 보존·광역화·지속 생성 등 표창 빌드 완성
Shared hooks already in use:
Rogue 단계에서도 분기 방향을 미리 경험할 수 있도록 약한 입문 카드를 유지합니다.
- 중독: `PoisonedStab`
- 표창: `LeadingStrike`
- 교활: `Untouchable`
## 스킬 카드 고정
실제 직업 스킬을 바탕으로 추가한 아래 카드는 다른 차수나 계열로 이동하지 않습니다.
- Rogue: `DoubleStab`, `LuckySeven`, `Haste`, `DarkSight`, `FlashJump`, `NimbleBody`
- Thief: `SavageBlow` 포함 9장
- Thief Master: `EdgeCarnival` 포함 11장
- Assassin: `ShurikenBurst` 포함 10장
- Hermit: `TripleThrow` 포함 9장
나머지 비스킬 카드는 컨셉에 맞춰 상위 직업으로 이동할 수 있습니다. 상위 직업은 하위 직업 카드를 함께 사용하므로, 이동은 해당 분기의 보상 풀을 제한하는 역할을 합니다.
## 공용 효과 필드
- `poison`, `innate`, `playableWhenDrawPileEmpty`
- `retain`, `sly`, `discard`, `discardAll`, `addShiv`, `addShivPerDiscard`, `turnStartShiv`, `retainOne`
@@ -17,6 +39,34 @@ Shared hooks already in use:
- `firstCardDamageBonus`
- `drawDamage`, `drawPoison`, `shivDamageBonus`, `firstShivDamageBonus`, `shivRetain`, `shivAoe`, `attackDamageVsWeakMultiplier`, `poisonHits`, `poisonRandomTargets`, `skillSlyOnPlay`, `extraPoisonTicks`, `poisonApplicationBurstEvery`, `poisonApplicationBurstDamage`
## Open questions
## 중복 제거 및 보정
None at the moment.
- 삭제: `Mirage`, `Accuracy`, `PhantomBlades`, `Adrenaline`, `Afterimage`, `Accelerant`, `Envenom`, `Tracking`
- 이유: 상위 직업 스킬 카드와 효과가 같거나, 비용 대비 열세라 별도 선택지가 되지 못함
- `Anticipate`: 턴 종료 시 얻은 민첩을 잃도록 실제 효과와 설명을 일치시킴
- `Backstab`, `Assassinate`, `TheHunt`, `PiercingWail`: 설명에 있던 소멸을 실제 필드에 반영
- 2차 지급: Thief `DaggerAcceleration`, Assassin `JavelinAcceleration`
- 3차 지급: Thief Master `Venom`, Hermit `SpiritJavelin`
## 카드 효율 검증
`node tools/balance/card-efficiency.mjs --runs 1000`으로 도적 계열 카드 전체를 검증합니다.
- 각 직업의 기준 덱에서 같은 종류의 카드 한 장을 교체하고 동일 시드로 반복 전투합니다.
- 승률, 승리 시 체력, 전투 턴을 합친 점수를 같은 직업·희귀도 중앙값과 비교합니다.
- 0코스트 에너지 생성, 재사용 가능한 영구 능력치, 저비용 2배 증폭처럼 자동 플레이가 놓치기 쉬운 구조도 별도로 검사합니다.
- 교활, 조건부 중독, 카드 보존처럼 플레이 순서 의존성이 큰 효과는 자동 시뮬레이션 하위권만으로 상향하지 않습니다.
2026-07-01 검증 결과 구조적 위험은 0장입니다. 주요 조정은 `독맥 터뜨리기`, `메아리 칼자국`, `소리 없는 제압`, `그리드`, `그림자 속도전`, `스틸`, 두 계열의 `피지컬 트레이닝`, `마크 오브 어쌔신`, `자벨린 액셀레이션`, `비장의 패`에 반영했습니다.
비스킬 카드 78장의 메이플풍 표시 이름은 `docs/rogue-card-names.md`에서 관리합니다. 메이플 원본 스킬 카드 45장의 이름은 변경하지 않습니다.
## 5섹션 캠페인 검증
`node tools/balance/rogue-campaign.mjs --runs 5000 --reward-min 5`로 전체 런을 검증합니다.
- 섹션마다 일반전 4회, 엘리트 1회, 보스 1회를 진행합니다.
- 1섹션은 Rogue, 2섹션은 2차 직업, 3~5섹션은 3차 직업 카드 풀을 사용합니다.
- 실제 카드 보상 확률, 전직 지급 카드, 시작·획득 유물, 체력 유지와 휴식 회복을 반영합니다.
- 몬스터 배율은 `1.00 → 1.075 → 1.15 → 1.30 → 1.45`이며 런타임과 시뮬레이터가 같은 공용 상수를 사용합니다.
- 5,000회 결과: Thief Master 완주 2.9%, Hermit 완주 3.6%. 자동 플레이와 일부 공격형 유물 미구현을 감안한 보수적 결과입니다.

View File

@@ -6,15 +6,25 @@ The goal is to keep card behavior reusable instead of hardcoding one-off card na
## Damage
- `damage`: base attack damage
- `damageFromCurrentBlock`: add current block times this value to attack damage
- `damageNameMatch`: substring to match against owned card names
- `damagePerOwnedNameMatch`: bonus damage per owned card whose name matches `damageNameMatch`
- `damagePerOtherHandCard`: bonus damage per other card in hand
- `damagePerAttackPlayedThisTurn`: bonus damage per attack played this turn
- `damagePerDiscardedThisTurn`: bonus damage per card discarded this turn
- `damagePerSkillInHand`: bonus damage per skill card in hand
- `damagePerCardDrawnThisCombat`: bonus damage per card drawn this combat
- `damagePerCombo`: bonus base damage per current Combo
- `damagePerHolyCharge`: bonus base damage per current Holy Charge
- `attackDamagePerCombo`: Power field that adds base damage per current Combo to all Attacks
- `attackPlayedDamage`: Power field that deals extra damage after an Attack card is played
- `damagePerTurn`: damage applied at turn start
- `cardPlayedDamage`: damage when the card is played
- `cardPlayedRandomDamage`: random damage when the card is played
- `drawOnExhaust`: draw when a card is exhausted
- `rewardOnKill`: gain bonus reward screens when the card kills
- `maxHpOnKill`: gain max HP when the attack kills
- `drawNameMatchAutoPlay`: auto-play drawn cards whose names contain this substring
- `randomTargetEachHit`: choose a random alive enemy for each hit
- `repeatOnKill`: repeat the attack when it kills at least one enemy
- `firstCardDamageBonus`: bonus damage for the first card played this turn
@@ -39,6 +49,17 @@ The goal is to keep card behavior reusable instead of hardcoding one-off card na
- `drawUntilHandSize`: draw until hand reaches a target size
- `drawSkillBlock`: gain block for each Skill drawn
- `drawPoison`: apply poison when a card is drawn
- `exhaustHandNonAttack`: exhaust every non-Attack card in hand
- `exhaustHandAll`: exhaust every card in hand
- `drawPerExhausted`: draw cards equal to exhausted cards
- `blockPerExhaustedCard`: gain block for each card exhausted by the current effect
- `addRandomCardCount`: add random cards to hand
- `addRandomCardPerExhausted`: add random cards equal to exhausted cards
- `addRandomCardKind`: filter random added cards by kind
- `addRandomCardSameClass`: restrict random added cards to the source card class
- `addedCardsCostZeroThisTurn`: cards added by this effect cost 0 this turn
- `playTopDrawPileCount`: play cards from the top of the draw pile
- `playTopDrawPileCountPerEnergy`: play cards from the top of the draw pile per energy spent
- `handCostZeroThisTurn`: make hand cards cost 0 this turn
- `drawDisabledThisTurn`: disable draw for the rest of the turn
- `heal`: heal immediately
@@ -48,6 +69,21 @@ The goal is to keep card behavior reusable instead of hardcoding one-off card na
- `thorns`: gain Thorns
- `selfVuln`: apply Vulnerable to self
- `extraPoisonTicks`: add extra poison ticks at enemy turn start
- `comboGain`: gain Combo when the card resolves; Attack cards gain it after dealing damage
- `comboOnAttack`: Power field that gains Combo whenever an Attack card is played
- `comboMax`: Power field that raises the maximum Combo above the default 5
- `attackWeak`: Power field that applies Weak after an Attack card is played
- `removePlayerDebuffs`: remove player Weak and Vulnerable
- `holyForce`: marks a card as Holy Force for Holy Charge Power triggers
- `holyChargeGain`: gain Holy Charge directly
- `holyChargeOnHolyForce`: Power field that gains Holy Charge after a Holy Force card
- `holyChargeMax`: Power field that raises the maximum Holy Charge above the default 3
- `blockPerHolyCharge`: gain additional block per current Holy Charge
- `healPerHolyCharge`: heal additional HP per current Holy Charge
- `holyChargeSpendAll`: consume all Holy Charge after resolving the card
- `damageTakenReduction`: Power field that reduces incoming damage; total reduction is capped at 75%
- `blockOnDamaged`: Power field that grants block after taking HP damage
- `strengthOnDamagedOnce`: Power field that grants Strength on the first HP damage each combat
## Status
@@ -91,7 +127,9 @@ The goal is to keep card behavior reusable instead of hardcoding one-off card na
- `powerEffect: "blockPerTurn"`
- `powerEffect: "poisonPerTurn"`
- `powerEffect: "damagePerTurn"`
- `powerEffect: "healPerTurn"`
- `powerEffect: "retainOne"`
- `powerEffect: "keepBlock"`
- `turnStartShiv`: create Shivs at turn start
- `turnStartDraw`: draw cards at turn start
- `turnStartDiscard`: discard cards at turn start

97
docs/rogue-card-names.md Normal file
View File

@@ -0,0 +1,97 @@
# 도적 비스킬 카드 이름
메이플스토리 원본 스킬을 바탕으로 만든 카드 45장은 이름을 고정합니다.
아래 78장은 다른 직업의 스킬명을 점유하지 않도록 도적 계열의 독자적인 이름으로 변경했습니다.
## Rogue
- `Neutralize`: 무력화 -> 커닝식 견제
- `SilentStrike`: 타격 -> 초보 도적의 칼끝
- `Survivor`: 생존자 -> 골목길 생존술
- `SilentDefend`: 수비 -> 낡은 가죽 방패
- `Slice`: 칼질 -> 짧은 단검질
- `PoisonedStab`: 독 찌르기 -> 초록 독단검
- `SuckerPunch`: 불의의 일격 -> 골목 기습
- `LeadingStrike`: 선제 타격 -> 초보 표창 던지기
- `Anticipate`: 예측 -> 럭키 예감
- `Deflect`: 튕겨내기 -> 단검 쳐내기
- `Backflip`: 공중제비 -> 커닝 곡예
- `DodgeAndRoll`: 구르기 -> 골목 구르기
- `Untouchable`: 범접 불가 -> 연막 속 숨기
- `Backstab`: 배신 -> 그림자 등찌르기
- `EscapePlan`: 탈출구 -> 비상용 연막탄
## Thief
- `DaggerSpray`: 단검 분사 -> 단검비
- `DaggerThrow`: 단검 투척 -> 비도 투척
- `FollowThrough`: 완수 -> 연달아 찌르기
- `FlickFlack`: 재주넘기 -> 커닝 난무
- `Prepared`: 예비 -> 비장의 패
- `PiercingWail`: 귀를 찢는 비명 -> 골목의 살기
- `DeadlyPoison`: 맹독 -> 맹독 조제
- `Snakebite`: 뱀 물기 -> 독니 단검
- `PreciseCut`: 정밀한 베기 -> 급소 절개
- `Finisher`: 마무리 -> 마지막 칼끝
- `MementoMori`: 메멘토 모리 -> 사신의 장부
- `Strangle`: 목 조르기 -> 그림자 올가미
- `Dash`: 돌진 -> 뒷골목 돌파
- `CalculatedGamble`: 계산된 도박 -> 메소 건 승부
- `Expose`: 들춰내기 -> 약점 들추기
- `Acrobatics`: 곡예 -> 지붕 위 곡예
- `HandTrick`: 손기술 -> 재빠른 손놀림
- `Expertise`: 전문성 -> 노련한 단검술
- `BubbleBubble`: 차오르는 독 -> 독액 농축
- `Blur`: 흐릿함 -> 흐린 잔영
- `LegSweep`: 다리 걸기 -> 발목 베기
- `Reflex`: 반사신경 -> 찰나의 반응
- `Tactician`: 전략가 -> 골목길 책략
- `WellLaidPlans`: 괜찮은 전략 -> 빈틈없는 작전
- `Footwork`: 발놀림 -> 사뿐한 발놀림
- `NoxiousFumes`: 유독 가스 -> 숨막히는 독연기
## Thief Master
- `BouncingFlask`: 탄성 플라스크 -> 통통 독병
- `Haze`: 아지랑이 -> 보랏빛 독연기
- `Outbreak`: 발병 -> 독맥 터뜨리기
- `Speedster`: 스피드스터 -> 그림자 속도전
- `GrandFinale`: 대단원의 막 -> 커닝의 대단원
- `Assassinate`: 암살 -> 어둠 속 급소
- `EchoingSlash`: 메아리 참격 -> 메아리 칼자국
- `Murder`: 살해 -> 쌓여가는 살의
- `Malaise`: 불쾌 -> 기운 빼는 독
- `ShadowStep`: 그림자 걸음 -> 그림자 발자국
- `Shadowmeld`: 그림자 은신 -> 연막 속 은신
- `CorrosiveWave`: 부식성 파도 -> 부식 독물결
- `Burst`: 폭주 -> 연속 술수
- `KnifeTrap`: 칼날 함정 -> 숨은 칼날덫
- `BulletTime`: 불릿 타임 -> 멈춘 듯한 순간
- `Nightmare`: 악몽 -> 검은 꿈
- `ToolsOfTheTrade`: 작업 도구 -> 도적의 연장통
- `MasterPlanner`: 설계의 대가 -> 작전의 달인
- `SerpentForm`: 구렁이의 형상 -> 독사의 몸놀림
- `Abrasive`: 연마 -> 거친 숫돌질
- `Suppress`: 진압 -> 소리 없는 제압
- `WraithForm`: 유령의 형상 -> 유령 같은 몸놀림
## Assassin
- `Ricochet`: 도탄 -> 통통 튀는 표창
- `BladeDance`: 검무 -> 표창 별무리
- `CloakAndDagger`: 망토와 단검 -> 망토 속 별
- `Skewer`: 꼬챙이 -> 꿰뚫는 표창
- `Flechettes`: 프레췌 -> 표창 셈법
- `Pounce`: 덮치기 -> 어둠을 가르는 도약
- `Predator`: 천적 -> 표창 끝의 추격
- `Pinpoint`: 정밀 사격 -> 한 점 겨냥
- `HiddenDaggers`: 숨겨진 표창 -> 숨겨둔 표창
- `UpMySleeve`: 비책 -> 소매 속 표창
- `InfiniteBlades`: 무한의 검날 -> 끝없는 표창통
- `TheHunt`: 사냥 -> 커닝 현상금
- `StormOfSteel`: 강철의 폭풍 -> 쇠별 폭풍
## Hermit
- `BladeOfInk`: 잉크 칼날 -> 먹빛 표창
- `FanOfKnives`: 칼날 부채 -> 사방 표창비

File diff suppressed because it is too large Load Diff

View File

@@ -6365,11 +6365,11 @@
},
{
"id": "000009c4-0000-4000-8000-0000000009c4",
"path": "/maps/map02/combat_1",
"path": "/maps/map02/pig",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "combat_1",
"path": "/maps/map02/combat_1",
"name": "pig",
"path": "/maps/map02/pig",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6379,12 +6379,12 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"entry_id": "monster-pig",
"sub_entity_id": null,
"root_entity_id": "000009c4-0000-4000-8000-0000000009c4",
"replaced_model_id": null
},
"modelId": "chasemonster",
"modelId": "monster-pig",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
@@ -6409,9 +6409,11 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "d8f014043ce8418f96700c2b6c9ebf6c",
"hit": "c3cf643b618346c7bfa6574187b396f9",
"die": "a88d9b3d60f941e4890dc89a6ccaa8ee"
"stand": "528a8638b12f41b8b5781a05360d2949",
"move": "8baad61512be4b33b2a0879fec7a266e",
"hit": "60e42a918a0342478903cc71adba1dc5",
"jump": "c9e27ce6f8344aefba169c5ca6571def",
"die": "0644beff80a44ec7acc011ea0961df57"
},
"Enable": true
},
@@ -6421,7 +6423,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "d8f014043ce8418f96700c2b6c9ebf6c",
"SpriteRUID": "528a8638b12f41b8b5781a05360d2949",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6517,11 +6519,11 @@
},
{
"id": "000009c5-0000-4000-8000-0000000009c5",
"path": "/maps/map02/combat_2",
"path": "/maps/map02/green_mushroom",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "combat_2",
"path": "/maps/map02/combat_2",
"name": "green_mushroom",
"path": "/maps/map02/green_mushroom",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6531,17 +6533,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"entry_id": "monster-green_mushroom",
"sub_entity_id": null,
"root_entity_id": "000009c5-0000-4000-8000-0000000009c5",
"replaced_model_id": null
},
"modelId": "chasemonster",
"modelId": "monster-green_mushroom",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 3.8,
"x": 3.375,
"y": 0.03499998,
"z": 999.999
},
@@ -6607,8 +6609,8 @@
"y": 0.58
},
"ColliderOffset": {
"x": 0.0449999869,
"y": 0.29
"x": 0.00999999,
"y": 0.26
},
"IsLegacy": false,
"Enable": true
@@ -6669,11 +6671,11 @@
},
{
"id": "000009c6-0000-4000-8000-0000000009c6",
"path": "/maps/map02/combat_3",
"path": "/maps/map02/blue_mushroom",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "combat_3",
"path": "/maps/map02/combat_3",
"name": "blue_mushroom",
"path": "/maps/map02/blue_mushroom",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6683,17 +6685,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"entry_id": "monster-blue_mushroom",
"sub_entity_id": null,
"root_entity_id": "000009c6-0000-4000-8000-0000000009c6",
"replaced_model_id": null
},
"modelId": "chasemonster",
"modelId": "monster-blue_mushroom",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 5.2,
"x": 4.449999999999999,
"y": 0.03499998,
"z": 999.999
},
@@ -6713,9 +6715,11 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "a2204a21d88942b281d2cac6053ffbaa",
"hit": "afc08936b8a64b26bc3dd8c03ead1f26",
"die": "fc1c6d9ba9bc413ab53b6dbfae3ac45b"
"stand": "1a176a7afb114fe7aef2bc58ef2d945b",
"move": "8239541953a6457fbe6d35e17f19f0f8",
"hit": "7b405108d05741699893a4dc3d715165",
"jump": "a7ea0755262242199ae50ab6a3387034",
"die": "9e74e807797d442f9c938ca64aa9f4cd"
},
"Enable": true
},
@@ -6725,7 +6729,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "a2204a21d88942b281d2cac6053ffbaa",
"SpriteRUID": "1a176a7afb114fe7aef2bc58ef2d945b",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6812,7 +6816,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "stump",
"EnemyId": "blue_mushroom",
"Group": "combat"
}
],
@@ -6821,11 +6825,11 @@
},
{
"id": "000009c7-0000-4000-8000-0000000009c7",
"path": "/maps/map02/elite_4",
"path": "/maps/map02/orange_mushroom",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "elite_4",
"path": "/maps/map02/elite_4",
"name": "orange_mushroom",
"path": "/maps/map02/orange_mushroom",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6835,17 +6839,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"entry_id": "monster-orange_mushroom",
"sub_entity_id": null,
"root_entity_id": "000009c7-0000-4000-8000-0000000009c7",
"replaced_model_id": null
},
"modelId": "chasemonster",
"modelId": "monster-orange_mushroom",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 3,
"x": 5.5249999999999995,
"y": 0.03499998,
"z": 999.999
},
@@ -6865,9 +6869,11 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "48c10437ae8344a9b2a1d3f36185728f",
"hit": "9044063647854f5e9128efcf80e909be",
"die": "f414577d18c94cc387c275df4abdbc3b"
"move": "573fe938562a4abf91eebf951f21afd5",
"stand": "6d381bea1bcb4504b518a1fbfa0904ac",
"jump": "59823e146a034e48b8667ebb6f0724b1",
"hit": "642ece38d8d449b29ce4479100e37a54",
"die": "3c99d6b9b89b4295a9c2749eb02e28e9"
},
"Enable": true
},
@@ -6877,7 +6883,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "48c10437ae8344a9b2a1d3f36185728f",
"SpriteRUID": "6d381bea1bcb4504b518a1fbfa0904ac",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6964,8 +6970,8 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "modified_snail",
"Group": "elite"
"EnemyId": "orange_mushroom",
"Group": "combat"
}
],
"@version": 1
@@ -6973,11 +6979,11 @@
},
{
"id": "000009c8-0000-4000-8000-0000000009c8",
"path": "/maps/map02/elite_5",
"path": "/maps/map02/slime",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "elite_5",
"path": "/maps/map02/elite_5",
"name": "slime",
"path": "/maps/map02/slime",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6987,17 +6993,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"entry_id": "monster-slime",
"sub_entity_id": null,
"root_entity_id": "000009c8-0000-4000-8000-0000000009c8",
"replaced_model_id": null
},
"modelId": "chasemonster",
"modelId": "monster-slime",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 5,
"x": 6.6,
"y": 0.03499998,
"z": 999.999
},
@@ -7017,9 +7023,11 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "ed3908e24d694bb786023fc1ed073489",
"hit": "4763c9bebc9245998c9c499b6316aa9f",
"die": "b168793b92a844a3a3a6f4ce647a14d2"
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
},
"Enable": true
},
@@ -7029,7 +7037,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "ed3908e24d694bb786023fc1ed073489",
"SpriteRUID": "50faf654ee5d479cb2958edce9feaef0",
"StartFrameIndex": 0,
"Enable": true
},
@@ -7113,6 +7121,161 @@
},
"Enable": true
},
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "slime",
"Group": "combat"
}
],
"@version": 1
}
},
{
"id": "000009c9-0000-4000-8000-0000000009c9",
"path": "/maps/map02/mushmom",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "mushmom",
"path": "/maps/map02/mushmom",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 4,
"pathConstraints": "///",
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-mushmom",
"sub_entity_id": null,
"root_entity_id": "000009c9-0000-4000-8000-0000000009c9",
"replaced_model_id": null
},
"modelId": "monster-mushmom",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4.3,
"y": 0.03499998,
"z": 999.999
},
"QuaternionRotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"Scale": {
"x": 1,
"y": 1,
"z": 1
},
"Enable": true
},
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "23c38ef3acad4a30ad59120bb939b008",
"move": "24d8a3a75f96406ba690ed42d7250b8f",
"hit": "c826e36ee89c48bca6aab856aa773f38",
"attack": "4d7465e950144dc59c263aad01b14e14",
"jump": "b7ddbda71a294141ba134249fc34c7da",
"die": "f50664a4524147399359cb90a6f3e80c"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "23c38ef3acad4a30ad59120bb939b008",
"StartFrameIndex": 0,
"Enable": true
},
{
"@type": "MOD.Core.RigidbodyComponent",
"MoveVelocity": {
"x": 0,
"y": 0
},
"RealMoveVelocity": {
"x": 0,
"y": 0
},
"Enable": true
},
{
"@type": "MOD.Core.MovementComponent",
"InputSpeed": 0,
"JumpForce": 6,
"Enable": false
},
{
"@type": "MOD.Core.StateComponent",
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 1.2,
"y": 1.1
},
"ColliderOffset": {
"x": 0.02,
"y": 0.55
},
"IsLegacy": false,
"Enable": true
},
{
"@type": "MOD.Core.DamageSkinSpawnerComponent",
"Enable": true
},
{
"@type": "script.Monster",
"Enable": true,
"IsDead": false
},
{
"@type": "script.MonsterAttack",
"Enable": true,
"SpriteSize": {
"x": 0,
"y": 0
},
"PositionOffset": {
"x": 0,
"y": 0
}
},
{
"@type": "MOD.Core.KinematicbodyComponent",
"MoveVelocity": {
"x": 0,
"y": 0
},
"Enable": true
},
{
"@type": "MOD.Core.SideviewbodyComponent",
"MoveVelocity": {
"x": 0,
"y": 0
},
"Enable": true
},
{
"@type": "MOD.Core.DamageSkinSettingComponent",
"DamageSkinId": {
"DataId": "02c22d93421b4038b3c413b3e40b57ec"
},
"Enable": true
},
{
"@type": "script.CombatMonster",
"Enable": true,
@@ -7124,12 +7287,12 @@
}
},
{
"id": "000009c9-0000-4000-8000-0000000009c9",
"path": "/maps/map02/boss_6",
"id": "000009ca-0000-4000-8000-0000000009ca",
"path": "/maps/map02/slime_boss",
"componentNames": "MOD.Core.TransformComponent,MOD.Core.StateAnimationComponent,MOD.Core.SpriteRendererComponent,MOD.Core.RigidbodyComponent,MOD.Core.MovementComponent,MOD.Core.StateComponent,MOD.Core.HitComponent,MOD.Core.DamageSkinSpawnerComponent,script.Monster,script.MonsterAttack,MOD.Core.KinematicbodyComponent,MOD.Core.SideviewbodyComponent,MOD.Core.DamageSkinSettingComponent,script.CombatMonster",
"jsonString": {
"name": "boss_6",
"path": "/maps/map02/boss_6",
"name": "slime_boss",
"path": "/maps/map02/slime_boss",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -7139,17 +7302,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"entry_id": "monster-slime_boss",
"sub_entity_id": null,
"root_entity_id": "000009c9-0000-4000-8000-0000000009c9",
"root_entity_id": "000009ca-0000-4000-8000-0000000009ca",
"replaced_model_id": null
},
"modelId": "chasemonster",
"modelId": "monster-slime_boss",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4,
"x": 4.6,
"y": 0.03499998,
"z": 999.999
},
@@ -7169,9 +7332,11 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "17b55730c26f4fd6b8fcfa288da388de",
"hit": "eac48e84a9fc4580a4018de5cf52ddb3",
"die": "51c2f4b59a2c413db26035aa57002fc8"
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
},
"Enable": true
},
@@ -7181,7 +7346,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "17b55730c26f4fd6b8fcfa288da388de",
"SpriteRUID": "50faf654ee5d479cb2958edce9feaef0",
"StartFrameIndex": 0,
"Enable": true
},
@@ -7211,12 +7376,12 @@
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 0.63,
"y": 0.58
"x": 1.2,
"y": 1.1
},
"ColliderOffset": {
"x": 0.0449999869,
"y": 0.29
"y": 0.4
},
"IsLegacy": false,
"Enable": true

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,275 @@
import { readFileSync } from 'node:fs';
import {
PLAYER_HP,
loadData,
mulberry32,
simulateCombat,
} from './sim-balance.mjs';
const AUDITED_CLASSES = new Set([
'rogue', 'thief', 'thiefmaster', 'assassin', 'hermit',
'warrior', 'fighter', 'crusader', 'page', 'knight',
]);
const CONTEXT_DECKS = {
rogue: [
'SilentStrike', 'SilentStrike', 'SilentStrike', 'SilentStrike',
'SilentDefend', 'SilentDefend', 'SilentDefend', 'SilentDefend',
'Neutralize', 'Survivor', 'DoubleStab', 'Backflip',
],
thief: [
'SilentStrike', 'SilentStrike', 'SilentStrike',
'SilentDefend', 'SilentDefend', 'SilentDefend',
'Neutralize', 'Survivor', 'SavageBlow', 'DaggerAcceleration',
'DeadlyPoison', 'Acrobatics',
],
thiefmaster: [
'SilentStrike', 'SilentStrike',
'SilentDefend', 'SilentDefend',
'Survivor', 'SavageBlow', 'DaggerAcceleration', 'DeadlyPoison',
'Acrobatics', 'EdgeCarnival', 'PickPocket', 'Venom',
],
assassin: [
'SilentStrike', 'SilentStrike', 'SilentStrike',
'SilentDefend', 'SilentDefend', 'SilentDefend',
'Neutralize', 'Survivor', 'LeadingStrike', 'BladeDance',
'JavelinAcceleration', 'JavelinMastery',
],
hermit: [
'SilentStrike', 'SilentStrike',
'SilentDefend', 'SilentDefend',
'Survivor', 'LeadingStrike', 'BladeDance', 'JavelinAcceleration',
'JavelinMastery', 'TripleThrow', 'SpiritJavelin', 'SkilledJavelin',
],
warrior: [
'Strike', 'Strike', 'Strike', 'Strike',
'Defend', 'Defend', 'Defend', 'Defend',
'Bash', 'SlashBlast', 'IronBody', 'WarriorMastery',
],
fighter: [
'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend',
'Bash', 'SlashBlast', 'ComboAttack', 'Brandish', 'WeaponMastery', 'FlashSlash',
],
crusader: [
'Strike', 'Strike', 'Defend', 'Defend', 'Bash', 'SlashBlast',
'ComboAttack', 'Brandish', 'WeaponMastery', 'BraveSlash', 'ComboSynergy', 'Rush',
],
page: [
'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend',
'Bash', 'SlashBlast', 'HolyCharge', 'DivineSwing', 'PageOrder', 'PageStance',
],
knight: [
'Strike', 'Strike', 'Defend', 'Defend', 'Bash', 'SlashBlast',
'HolyCharge', 'DivineSwing', 'PageOrder', 'DivineCharge', 'KnightRush', 'Restoration',
],
};
const ENCOUNTER_SCALE = {
rogue: { hp: 1.9, attack: 1.5 },
thief: { hp: 2.2, attack: 1.6 },
assassin: { hp: 2.25, attack: 1.65 },
thiefmaster: { hp: 2.4, attack: 1.5 },
hermit: { hp: 2.6, attack: 1.65 },
warrior: { hp: 1.9, attack: 1.5 },
fighter: { hp: 2.2, attack: 1.6 },
crusader: { hp: 2.6, attack: 1.7 },
page: { hp: 2.2, attack: 1.6 },
knight: { hp: 2.6, attack: 1.7 },
};
const median = (values) => {
if (values.length === 0) return 0;
const sorted = values.slice().sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
return sorted.length % 2 === 1
? sorted[middle]
: (sorted[middle - 1] + sorted[middle]) / 2;
};
function validateContextDecks(cards) {
for (const [classId, deck] of Object.entries(CONTEXT_DECKS)) {
for (const cardId of deck) {
if (!cards[cardId]) throw new Error(`${classId} 효율 기준 덱에 없는 카드: ${cardId}`);
}
}
}
function outcomeScore(result) {
if (result.draw) return -60;
if (!result.win) return -100 - result.turns;
return 100 + (result.playerHpRemaining / PLAYER_HP) * 30 - result.turns * 2;
}
function scaledEncounter(data, classId) {
const scale = ENCOUNTER_SCALE[classId];
return {
...data,
monsters: data.monsters.map((monster) => ({
...monster,
maxHp: Math.round(monster.maxHp * scale.hp),
intents: monster.intents.map((intent) => intent.kind === 'Attack'
? { ...intent, value: Math.round(intent.value * scale.attack) }
: { ...intent }),
})),
};
}
function simulateDeck(baseData, deck, runs, seed, trackedCardId = null) {
let wins = 0;
let totalTurns = 0;
let totalHp = 0;
let totalScore = 0;
let totalPlays = 0;
for (let i = 0; i < runs; i++) {
const stats = {};
const rng = mulberry32((seed + Math.imul(i + 1, 0x9e3779b1)) >>> 0);
const result = simulateCombat({ ...baseData, starterDeck: deck }, rng, stats);
if (result.win) {
wins++;
totalHp += result.playerHpRemaining;
}
totalTurns += result.turns;
totalScore += outcomeScore(result);
if (trackedCardId && stats[trackedCardId]) totalPlays += stats[trackedCardId].plays;
}
return {
winRate: wins / runs,
avgTurns: totalTurns / runs,
avgHpOnWin: wins > 0 ? totalHp / wins : 0,
score: totalScore / runs,
avgPlays: totalPlays / runs,
};
}
function replacementIndex(deck, cards, candidate) {
const preferredKind = candidate.kind === 'Attack' ? 'Attack' : 'Skill';
const preferred = deck.findIndex((id) => cards[id]?.kind === preferredKind);
if (preferred >= 0) return preferred;
return 0;
}
export function structuralRisks(card) {
const risks = [];
const cost = card.cost || 0;
const exhaust = card.exhaust === true;
const permanentDex = Math.max(0, (card.dex || 0) - (card.endTurnDexLoss || 0));
const permanentStats = (card.strength || 0) + permanentDex + (card.thorns || 0);
const generatedCards = (card.addShiv || 0) + (card.addShivPerDiscard ? 1 : 0);
if (cost === 0 && !exhaust && (card.gainEnergy || 0) > 0) {
risks.push('0코스트 비소멸 카드가 에너지를 생성');
}
if (cost === 0 && !exhaust && (card.draw || 0) >= 2 && generatedCards > 0) {
risks.push('0코스트 비소멸 카드가 2장 이상 드로우하면서 카드를 생성');
}
if (card.kind !== 'Power' && !exhaust && permanentStats > 0) {
risks.push('재사용 가능한 카드가 영구 능력치를 누적');
}
if (card.kind === 'Power' && (card.attackDamageVsWeakMultiplier || 0) >= 2 && cost <= 1) {
risks.push('저비용 지속 효과가 공격 피해를 2배 이상 증폭');
}
if ((card.poisonApplicationBurstEvery || 0) > 0) {
const burstPerApplication = (card.poisonApplicationBurstDamage || 0) / card.poisonApplicationBurstEvery;
if (burstPerApplication > 3 && cost <= 1) {
risks.push('저비용 독 누적 폭발 피해가 부여 1회당 3을 초과');
}
}
if (cost === 0 && !exhaust && (card.block || 0) + (card.nextTurnBlock || 0) >= 8) {
risks.push('0코스트 비소멸 카드의 현재·다음 턴 방어 합계가 8 이상');
}
if (cost === 0 && !exhaust && (card.blockPerDamageDealtThisTurn || 0) >= 1) {
risks.push('0코스트 비소멸 카드가 이번 턴 누적 피해 전부를 방어로 전환');
}
if (!exhaust && (card.gainEnergy || 0) > 0 && (card.gainEnergy || 0) >= cost && (card.draw || 0) > 0 && generatedCards > 0) {
risks.push('에너지 손실 없이 드로우와 카드 생성을 동시에 수행');
}
if (!exhaust && (card.skillCostReductionThisTurn || 0) > 0 && (card.gainEnergy || 0) > 0 && (card.gainEnergy || 0) >= cost && (card.draw || 0) > 0) {
risks.push('에너지 손실 없이 드로우하고 이번 턴 스킬 비용까지 감소');
}
return risks;
}
export function auditCardEfficiency({ runs = 300, seed = 20260701 } = {}) {
const data = loadData();
const cards = data.cards;
validateContextDecks(cards);
const baselines = {};
for (const [classId, deck] of Object.entries(CONTEXT_DECKS)) {
baselines[classId] = simulateDeck(scaledEncounter(data, classId), deck, runs, seed);
}
const rows = [];
for (const [id, card] of Object.entries(cards)) {
if (!AUDITED_CLASSES.has(card.class)) continue;
const deck = CONTEXT_DECKS[card.class].slice();
deck[replacementIndex(deck, cards, card)] = id;
const result = simulateDeck(scaledEncounter(data, card.class), deck, runs, seed, id);
rows.push({
id,
name: card.name,
classId: card.class,
rarity: card.rarity,
kind: card.kind,
cost: card.cost || 0,
delta: result.score - baselines[card.class].score,
...result,
risks: structuralRisks(card),
});
}
for (const row of rows) {
const peers = rows.filter((other) => other.classId === row.classId && other.rarity === row.rarity);
row.peerMedianDelta = median(peers.map((peer) => peer.delta));
row.peerGap = row.delta - row.peerMedianDelta;
}
return { runs, seed, baselines, rows };
}
function formatPercent(value) {
return `${(value * 100).toFixed(1)}%`;
}
export function formatEfficiencyReport(report) {
const lines = [];
lines.push(`카드 효율 검증: 카드 ${report.rows.length}장, 카드당 ${report.runs}`);
lines.push('기준 덱:');
for (const [classId, baseline] of Object.entries(report.baselines)) {
lines.push(` ${classId}: 승률 ${formatPercent(baseline.winRate)}, 평균 ${baseline.avgTurns.toFixed(2)}턴, 승리 HP ${baseline.avgHpOnWin.toFixed(1)}`);
}
const risky = report.rows.filter((row) => row.risks.length > 0);
lines.push('');
lines.push(`구조적 위험 ${risky.length}장:`);
for (const row of risky) {
lines.push(` ${row.name}(${row.id}, ${row.classId}): ${row.risks.join(' / ')}`);
}
lines.push('');
lines.push('동급 대비 효율 상위:');
for (const row of report.rows.slice().sort((a, b) => b.peerGap - a.peerGap).slice(0, 10)) {
lines.push(` ${row.name}(${row.id}): 중앙값 대비 +${row.peerGap.toFixed(1)}, 승률 ${formatPercent(row.winRate)}, 평균 사용 ${row.avgPlays.toFixed(2)}`);
}
lines.push('');
lines.push('동급 대비 효율 하위:');
for (const row of report.rows.slice().sort((a, b) => a.peerGap - b.peerGap).slice(0, 10)) {
lines.push(` ${row.name}(${row.id}): 중앙값 대비 ${row.peerGap.toFixed(1)}, 승률 ${formatPercent(row.winRate)}, 평균 사용 ${row.avgPlays.toFixed(2)}`);
}
return lines.join('\n');
}
function main() {
const args = process.argv.slice(2);
let runs = 300;
let seed = 20260701;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--runs') runs = Number.parseInt(args[++i], 10);
else if (args[i] === '--seed') seed = Number.parseInt(args[++i], 10);
}
const report = auditCardEfficiency({ runs, seed });
console.log(formatEfficiencyReport(report));
if (report.rows.some((row) => row.risks.length > 0)) process.exitCode = 1;
}
if (process.argv[1] && process.argv[1].endsWith('card-efficiency.mjs')) main();

View File

@@ -0,0 +1,30 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { structuralRisks } from './card-efficiency.mjs';
test('0코스트 에너지 생성 카드를 위험으로 분류', () => {
const risks = structuralRisks({ cost: 0, kind: 'Skill', gainEnergy: 1 });
assert.ok(risks.some((risk) => risk.includes('에너지를 생성')));
});
test('재사용 가능한 영구 능력치 스킬을 위험으로 분류', () => {
const risks = structuralRisks({ cost: 1, kind: 'Skill', strength: 1, dex: 1 });
assert.ok(risks.some((risk) => risk.includes('영구 능력치')));
});
test('소멸하거나 파워인 능력치 카드는 허용', () => {
assert.deepEqual(structuralRisks({ cost: 1, kind: 'Skill', strength: 1, exhaust: true }), []);
assert.deepEqual(structuralRisks({ cost: 1, kind: 'Power', dex: 1 }), []);
assert.deepEqual(structuralRisks({ cost: 0, kind: 'Skill', dex: 2, endTurnDexLoss: 2 }), []);
});
test('저비용 2배 피해 증폭을 위험으로 분류', () => {
const risks = structuralRisks({ cost: 1, kind: 'Power', attackDamageVsWeakMultiplier: 2 });
assert.ok(risks.some((risk) => risk.includes('2배')));
});
test('0코스트 누적 피해 전체 방어 전환을 위험으로 분류', () => {
const risks = structuralRisks({ cost: 0, kind: 'Skill', blockPerDamageDealtThisTurn: 1 });
assert.ok(risks.some((risk) => risk.includes('누적 피해')));
assert.deepEqual(structuralRisks({ cost: 0, kind: 'Skill', blockPerDamageDealtThisTurn: 0.5 }), []);
});

View File

@@ -0,0 +1,334 @@
import { readFileSync } from 'node:fs';
import { mulberry32, rarityForRoll, simulateCombat } from './sim-balance.mjs';
import { ACT_DIFFICULTY_MULTIPLIERS } from '../deck/lib/codeblock.mjs';
const cardsData = JSON.parse(readFileSync('data/cards.json', 'utf8'));
const enemiesData = JSON.parse(readFileSync('data/enemies.json', 'utf8'));
const relicsData = JSON.parse(readFileSync('data/relics.json', 'utf8'));
const PLAYER_MAX_HP = { rogue: 70, warrior: 80 };
const REST_HEAL = 30;
const SECTION_COUNT = 5;
const NORMAL_FIGHTS = 4;
export const DEFAULT_SECTION_MULTIPLIERS = ACT_DIFFICULTY_MULTIPLIERS;
const COMBAT_POOL = ['orange_mushroom', 'green_mushroom', 'pig', 'blue_mushroom', 'red_snail', 'stump'];
const ELITE_POOL = ['mushmom', 'modified_snail'];
const BOSS_POOL = ['king_slime', 'slime_boss'];
const JOBS = {
thief: { tier2: 'thief', tier3: 'thiefmaster', tier2Starter: 'DaggerAcceleration', tier3Starter: 'Venom' },
assassin: { tier2: 'assassin', tier3: 'hermit', tier2Starter: 'JavelinAcceleration', tier3Starter: 'SpiritJavelin' },
fighter: { root: 'warrior', tier2: 'fighter', tier3: 'crusader', tier2Starter: 'ComboAttack', tier3Starter: 'ComboSynergy' },
page: { root: 'warrior', tier2: 'page', tier3: 'knight', tier2Starter: 'HolyCharge', tier3Starter: 'DivineCharge' },
};
const LINEAGES = {
rogue: ['rogue'],
thief: ['rogue', 'thief'],
thiefmaster: ['rogue', 'thief', 'thiefmaster'],
assassin: ['rogue', 'assassin'],
hermit: ['rogue', 'assassin', 'hermit'],
warrior: ['warrior'],
fighter: ['warrior', 'fighter'],
crusader: ['warrior', 'fighter', 'crusader'],
page: ['warrior', 'page'],
knight: ['warrior', 'page', 'knight'],
};
const pick = (rng, values) => values[Math.floor(rng() * values.length)];
export function campaignJobAtSection(branch, section) {
if (section <= 1) return JOBS[branch].root || 'rogue';
if (section === 2) return JOBS[branch].tier2;
return JOBS[branch].tier3;
}
export function playableClassesForJob(job) {
return LINEAGES[job] || [job];
}
export function scaleEnemy(enemy, section, rng = () => 0, scaleStep = null) {
const multiplier = scaleStep == null
? (DEFAULT_SECTION_MULTIPLIERS[section - 1] || DEFAULT_SECTION_MULTIPLIERS.at(-1))
: 1 + (section - 1) * scaleStep;
const offset = enemy.intents.length > 0 ? Math.floor(rng() * enemy.intents.length) : 0;
const rotatedIntents = enemy.intents.map((_, index) => enemy.intents[(index + offset) % enemy.intents.length]);
return {
...enemy,
maxHp: Math.floor(enemy.maxHp * multiplier),
intents: rotatedIntents.map((intent) => ({
...intent,
value: intent.kind === 'Debuff' || intent.value == null
? intent.value
: Math.floor(intent.value * multiplier),
})),
};
}
function buildEncounter(kind, section, rng, scaleStep) {
const ids = [];
if (kind === 'normal') {
const count = 1 + Math.floor(rng() * 3);
for (let i = 0; i < count; i++) ids.push(pick(rng, COMBAT_POOL));
} else if (kind === 'elite') {
ids.push(pick(rng, ELITE_POOL));
const extra = Math.floor(rng() * 3);
for (let i = 0; i < extra; i++) ids.push(pick(rng, COMBAT_POOL));
} else {
ids.push(pick(rng, BOSS_POOL));
}
return ids.map((id) => scaleEnemy(enemiesData.enemies[id], section, rng, scaleStep));
}
function baseCardValue(card) {
const hits = card.hits || 1;
const targets = card.aoe ? 1.7 : 1;
let value = (card.damage || 0) * hits * targets;
value += (card.block || 0) + (card.nextTurnBlock || 0) * 0.7;
value += (card.poison || 0) * (card.poisonHits || 1) * (card.affectsAllEnemies ? 2 : 1) * 1.5;
value += (card.draw || 0) * 4 + (card.gainEnergy || 0) * 5;
value += (card.addShiv || 0) * 4;
value += (card.strength || 0) * 6 + (card.dex || 0) * 5;
value += (card.weak || 0) * 3 + (card.vuln || 0) * 4;
value += (card.intangible || 0) * 12;
value += (card.turnStartShiv || 0) * 8 + (card.shivDamageBonus || 0) * 4;
value += (card.cardPlayedBlock || 0) * 8 + (card.attackPoison || 0) * 8;
value += (card.powerEffect ? 7 : 0) + (card.retain ? 2 : 0) + (card.sly ? 3 : 0);
value += (card.damagePerDiscardedThisTurn || 0) * 2;
value += (card.damagePerAttackPlayedThisTurn || 0) * 2;
value += (card.firstShivDamageBonus || 0) * 2;
value -= (card.cost || 0) * 5;
if (card.exhaust) value -= 2;
return value;
}
function branchCardValue(card, branch, deck, id) {
let value = baseCardValue(card);
if (branch === 'thief') {
value += (card.poison || 0) * 1.5 + (card.attackPoison || 0) * 8;
value += card.sly ? 5 : 0;
value += (card.discard || 0) * 2 + (card.drawPerDiscarded || 0) * 4;
value += (card.poisonApplicationBurstDamage || 0) * 1.5;
} else if (branch === 'assassin') {
value += (card.addShiv || 0) * 3 + (card.turnStartShiv || 0) * 8;
value += (card.shivDamageBonus || 0) * 6 + (card.firstShivDamageBonus || 0) * 3;
value += card.shivAoe ? 12 : 0;
value += card.shivRetain ? 5 : 0;
} else if (branch === 'fighter') {
value += (card.hits || 1) * 1.5;
value += (card.comboGain || 0) * 5 + (card.comboOnAttack || 0) * 10;
value += (card.damagePerCombo || 0) * 8 + (card.attackDamagePerCombo || 0) * 12;
value += (card.attackPlayedDamage || 0) * 5;
} else if (branch === 'page') {
value += (card.block || 0) * 0.5 + (card.cardPlayedBlock || 0) * 7;
value += (card.holyChargeOnHolyForce || 0) * 12 + (card.damagePerHolyCharge || 0) * 7;
value += (card.blockPerHolyCharge || 0) * 6 + (card.healPerHolyCharge || 0) * 3;
value += (card.damageTakenReduction || 0) * 40;
value += (card.blockOnDamaged || 0) * 3 + (card.strengthOnDamagedOnce || 0) * 5;
}
const copies = deck.filter((cardId) => cardId === id).length;
value -= copies * (card.kind === 'Power' ? 10 : 3);
return value;
}
function rewardPool(job) {
const classes = new Set(playableClassesForJob(job));
return Object.entries(cardsData.cards)
.filter(([, card]) => classes.has(card.class) && card.token !== true && card.unplayable !== true);
}
function offerReward(job, branch, deck, rng, minimumValue) {
const pool = rewardPool(job);
const choices = [];
for (let i = 0; i < 3; i++) {
const rarity = rarityForRoll(1 + Math.floor(rng() * 100));
const bucket = pool.filter(([, card]) => card.rarity === rarity);
choices.push(pick(rng, bucket.length > 0 ? bucket : pool));
}
choices.sort((a, b) => branchCardValue(b[1], branch, deck, b[0]) - branchCardValue(a[1], branch, deck, a[0]));
const [id, card] = choices[0];
if (branchCardValue(card, branch, deck, id) >= minimumValue) deck.push(id);
}
function relicModifiers(state) {
const result = {
playerStartBlock: 0,
playerStrength: 0,
playerThorns: 0,
energyBonus: 0,
openingDrawBonus: 0,
healOnAttack: 0,
};
for (const id of state.relics) {
const relic = relicsData.relics[id];
if (!relic) continue;
if (relic.hook === 'combatStart' && relic.effect === 'block') result.playerStartBlock += relic.value;
else if (relic.hook === 'combatStart' && relic.effect === 'strength') result.playerStrength += relic.value;
else if (relic.hook === 'turnStart' && relic.effect === 'energy') result.energyBonus += relic.value;
else if (relic.hook === 'combatStart' && relic.effect === 'draw') result.openingDrawBonus += relic.value;
else if (relic.effect === 'thorns') result.playerThorns += relic.value;
else if (relic.effect === 'healOnAttack') result.healOnAttack += relic.value;
}
return result;
}
function healFromRelics(state, hook) {
for (const id of state.relics) {
const relic = relicsData.relics[id];
if (!relic || relic.hook !== hook) continue;
if (relic.effect === 'heal') state.hp = Math.min(state.maxHp, state.hp + relic.value);
else if (relic.effect === 'healOnWin') state.hp = Math.min(state.maxHp, state.hp + relic.value);
else if (relic.effect === 'healIfLow' && state.hp <= state.maxHp * 0.5) state.hp = Math.min(state.maxHp, state.hp + relic.value);
}
}
function acquireRelic(state, rng) {
const available = relicsData.relicPool.filter((id) => !state.relics.includes(id));
if (available.length === 0) return;
const id = pick(rng, available);
state.relics.push(id);
const relic = relicsData.relics[id];
if (relic?.effect === 'maxHp') {
state.maxHp += relic.value;
state.hp += relic.value;
}
}
function fight(state, branch, kind, section, rng, options) {
const monsters = buildEncounter(kind, section, rng, options.scaleStep);
healFromRelics(state, 'combatStart');
const result = simulateCombat({
cards: cardsData.cards,
starterDeck: state.deck,
monsters,
playerHp: state.hp,
playerMaxHp: state.maxHp,
smartPlayer: true,
...relicModifiers(state),
}, rng);
state.hp = result.playerHpRemaining;
state.turns += result.turns;
if (!result.win) return false;
healFromRelics(state, 'combatEnd');
if (kind !== 'boss') offerReward(state.job, branch, state.deck, rng, options.minimumRewardValue);
return true;
}
export function simulateCampaign(branch, rng, {
restHeal = REST_HEAL,
sectionHeal = 0,
scaleStep = null,
minimumRewardValue = 10,
} = {}) {
if (!JOBS[branch]) throw new Error(`지원하지 않는 도적 분기: ${branch}`);
const root = JOBS[branch].root || 'rogue';
const maxHp = PLAYER_MAX_HP[root];
const state = {
hp: maxHp,
maxHp,
deck: cardsData.starterDecks[root].slice(),
job: root,
turns: 0,
sectionCleared: 0,
diedAt: '',
hpAfterSections: [],
relics: [relicsData.startingRelic],
};
const options = { scaleStep, minimumRewardValue };
for (let section = 1; section <= SECTION_COUNT; section++) {
state.job = campaignJobAtSection(branch, section);
for (let fightIndex = 1; fightIndex <= NORMAL_FIGHTS; fightIndex++) {
if (!fight(state, branch, 'normal', section, rng, options)) {
state.diedAt = `${section}-normal`;
return state;
}
}
state.hp = Math.min(state.maxHp, state.hp + restHeal);
if (!fight(state, branch, 'elite', section, rng, options)) {
state.diedAt = `${section}-elite`;
return state;
}
acquireRelic(state, rng);
if (!fight(state, branch, 'boss', section, rng, options)) {
state.diedAt = `${section}-boss`;
return state;
}
state.sectionCleared = section;
state.hpAfterSections.push(state.hp);
if (section === 1) state.deck.push(JOBS[branch].tier2Starter);
if (section === 2) state.deck.push(JOBS[branch].tier3Starter);
if (section >= 3) acquireRelic(state, rng);
if (section < SECTION_COUNT) state.hp = Math.min(state.maxHp, state.hp + sectionHeal);
}
return state;
}
export function runCampaignBatch(branch, runs = 1000, seed = 20260701, options = {}) {
const sectionReached = Array(SECTION_COUNT).fill(0);
const sectionClears = Array(SECTION_COUNT).fill(0);
const deaths = {};
let fullClears = 0;
let totalDeckSize = 0;
let totalFinalHp = 0;
let totalTurns = 0;
for (let i = 0; i < runs; i++) {
const rng = mulberry32((seed + Math.imul(i + 1, 0x9e3779b1)) >>> 0);
const result = simulateCampaign(branch, rng, options);
for (let section = 0; section < SECTION_COUNT; section++) {
if (result.sectionCleared >= section) sectionReached[section]++;
if (result.sectionCleared >= section + 1) sectionClears[section]++;
}
if (result.sectionCleared === SECTION_COUNT) {
fullClears++;
totalFinalHp += result.hp;
}
if (result.diedAt) deaths[result.diedAt] = (deaths[result.diedAt] || 0) + 1;
totalDeckSize += result.deck.length;
totalTurns += result.turns;
}
return {
branch,
runs,
fullClearRate: fullClears / runs,
avgFinalHp: fullClears > 0 ? totalFinalHp / fullClears : 0,
avgDeckSize: totalDeckSize / runs,
avgTurns: totalTurns / runs,
sectionConditionalClearRates: sectionClears.map((clears, index) => sectionReached[index] > 0 ? clears / sectionReached[index] : 0),
sectionReachRates: sectionReached.map((reached) => reached / runs),
deaths,
};
}
export function formatCampaignReport(result) {
const lines = [];
lines.push(`${result.branch} 캠페인 ${result.runs}`);
lines.push(` 전체 클리어 ${(result.fullClearRate * 100).toFixed(1)}%, 클리어 HP ${result.avgFinalHp.toFixed(1)}, 평균 덱 ${result.avgDeckSize.toFixed(1)}`);
result.sectionConditionalClearRates.forEach((rate, index) => {
lines.push(` 섹션 ${index + 1}: 도달 ${(result.sectionReachRates[index] * 100).toFixed(1)}%, 도달자 클리어 ${(rate * 100).toFixed(1)}%`);
});
return lines.join('\n');
}
function main() {
const args = process.argv.slice(2);
let runs = 1000;
let seed = 20260701;
let restHeal = REST_HEAL;
let sectionHeal = 0;
let scaleStep = null;
let minimumRewardValue = 10;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--runs') runs = Number.parseInt(args[++i], 10);
else if (args[i] === '--seed') seed = Number.parseInt(args[++i], 10);
else if (args[i] === '--rest-heal') restHeal = Number.parseInt(args[++i], 10);
else if (args[i] === '--section-heal') sectionHeal = Number.parseInt(args[++i], 10);
else if (args[i] === '--scale-step') scaleStep = Number.parseFloat(args[++i]);
else if (args[i] === '--reward-min') minimumRewardValue = Number.parseFloat(args[++i]);
}
for (const branch of ['thief', 'assassin', 'fighter', 'page']) {
console.log(formatCampaignReport(runCampaignBatch(branch, runs, seed, { restHeal, sectionHeal, scaleStep, minimumRewardValue })));
}
}
if (process.argv[1] && process.argv[1].endsWith('rogue-campaign.mjs')) main();

View File

@@ -0,0 +1,38 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
campaignJobAtSection,
playableClassesForJob,
scaleEnemy,
} from './rogue-campaign.mjs';
test('도적 전직 시점: 1섹션 Rogue, 2섹션 2차, 3섹션부터 3차', () => {
assert.equal(campaignJobAtSection('thief', 1), 'rogue');
assert.equal(campaignJobAtSection('thief', 2), 'thief');
assert.equal(campaignJobAtSection('thief', 3), 'thiefmaster');
assert.equal(campaignJobAtSection('assassin', 2), 'assassin');
assert.equal(campaignJobAtSection('assassin', 5), 'hermit');
});
test('3차 직업은 자기 계보 카드만 사용', () => {
assert.deepEqual(playableClassesForJob('thiefmaster'), ['rogue', 'thief', 'thiefmaster']);
assert.deepEqual(playableClassesForJob('hermit'), ['rogue', 'assassin', 'hermit']);
assert.deepEqual(playableClassesForJob('crusader'), ['warrior', 'fighter', 'crusader']);
assert.deepEqual(playableClassesForJob('knight'), ['warrior', 'page', 'knight']);
});
test('전사 전직 시점: 1섹션 Warrior, 2섹션 2차, 3섹션부터 3차', () => {
assert.equal(campaignJobAtSection('fighter', 1), 'warrior');
assert.equal(campaignJobAtSection('fighter', 2), 'fighter');
assert.equal(campaignJobAtSection('fighter', 3), 'crusader');
assert.equal(campaignJobAtSection('page', 2), 'page');
assert.equal(campaignJobAtSection('page', 5), 'knight');
});
test('섹션 난이도는 3차 이후 더 빠르게 증가', () => {
const enemy = { maxHp: 100, intents: [{ kind: 'Attack', value: 10 }, { kind: 'Debuff', value: 2 }] };
const scaled = scaleEnemy(enemy, 3, () => 0);
assert.equal(scaled.maxHp, 114);
assert.equal(scaled.intents[0].value, 11);
assert.equal(scaled.intents[1].value, 2);
});

View File

@@ -99,7 +99,7 @@ export function chooseAction(hand, cards, energy, ctx = {}) {
const card = cards[x.id];
if (!card || card.unplayable || !canPlayCardNow(card, ctx)) return false;
let effectiveCost = card.cost || 0;
if (ctx.handCostZeroThisTurn === true) effectiveCost = 0;
if (ctx.handCostZeroThisTurn === true || ctx.zeroCostCardIdsThisTurn?.has(x.id) === true) effectiveCost = 0;
else if (card.useAllEnergy === true) effectiveCost = 1;
else if (card.kind === 'Skill') {
if (ctx.nextSkillCostZero === true) effectiveCost = 0;
@@ -116,7 +116,7 @@ export function chooseAction(hand, cards, energy, ctx = {}) {
const effectiveCost = (x) => {
const card = cards[x.id];
let cost = card.cost || 0;
if (ctx.handCostZeroThisTurn === true) cost = 0;
if (ctx.handCostZeroThisTurn === true || ctx.zeroCostCardIdsThisTurn?.has(x.id) === true) cost = 0;
else if (card.useAllEnergy === true) cost = 1;
else if (card.kind === 'Skill') {
if (ctx.nextSkillCostZero === true) cost = 0;
@@ -127,12 +127,70 @@ export function chooseAction(hand, cards, energy, ctx = {}) {
}
return cost;
};
const dmgEff = (x) => (cards[x.id].damage || 0) / Math.max(effectiveCost(x), 1);
const countOtherHandSkills = (currentId) => {
let n = 0;
let skippedSelf = false;
for (const id of hand) {
if (!skippedSelf && id === currentId) {
skippedSelf = true;
continue;
}
if (cards[id]?.kind === 'Skill') n++;
}
return n;
};
const countOwnedNameMatches = (match) => {
if (!match) return 0;
let n = 0;
for (const id of hand) {
const name = cards[id]?.name || '';
if (name.includes(match)) n++;
}
for (const pile of [ctx.drawPileCards || [], ctx.discardCards || [], ctx.exhaustCards || []]) {
for (const id of pile) {
const name = cards[id]?.name || '';
if (name.includes(match)) n++;
}
}
return n;
};
const attackBaseEstimate = (x) => {
const card = cards[x.id];
let base = card.damage || 0;
base += (ctx.currentBlock || 0) * (card.damageFromCurrentBlock || 0);
if (card.damageNameMatch && card.damagePerOwnedNameMatch) {
base += countOwnedNameMatches(card.damageNameMatch) * card.damagePerOwnedNameMatch;
}
base += Math.max(0, hand.length - 1) * (card.damagePerOtherHandCard || 0);
base += (ctx.turnAttackCardsPlayed || 0) * (card.damagePerAttackPlayedThisTurn || 0);
base += countOtherHandSkills(x.id) * (card.damagePerSkillInHand || 0);
base += (ctx.cardsDrawnThisCombat || 0) * (card.damagePerCardDrawnThisCombat || 0);
if (base < 0) base = 0;
return base;
};
const dmgEff = (x) => attackBaseEstimate(x) / Math.max(effectiveCost(x), 1);
const blkEff = (x) => (cards[x.id].block || 0) / Math.max(effectiveCost(x), 1);
const bestBy = (list, fn) => list.slice().sort((a, b) => fn(b) - fn(a))[0];
if ((ctx.incomingDamage || 0) > (ctx.currentBlock || 0)) {
const defensive = entries.filter((x) => {
const card = cards[x.id];
return (card.block || 0) > 0 || (card.intangible || 0) > 0 || (card.enemyStrengthLossThisTurn || 0) > 0;
});
if (defensive.length) {
return bestBy(defensive, (x) => {
const card = cards[x.id];
const protection = (card.block || 0) + (card.intangible || 0) * 15 + (card.enemyStrengthLossThisTurn || 0) * 2;
return protection / Math.max(effectiveCost(x), 1);
}).i;
}
}
if (powers.length) return powers[0].i;
if (attacks.length) return bestBy(attacks, dmgEff).i;
if (attacks.length) {
const bestAttack = bestBy(attacks, dmgEff);
if (bestAttack && dmgEff(bestAttack) > 0) return bestAttack.i;
}
if (skills.length) return bestBy(skills, blkEff).i;
if (attacks.length) return bestBy(attacks, dmgEff).i;
return -1;
}
@@ -154,13 +212,16 @@ function bump(s, cost, dmg, blk) {
// 반환: { win, turns, playerHpRemaining, draw? }
export function simulateCombat(data, rng, stats) {
const { cards, starterDeck, monsters } = data;
if (monsters.length === 0) return { win: true, turns: 0, playerHpRemaining: PLAYER_HP };
let playerMaxHp = data.playerMaxHp || PLAYER_HP;
const startingPlayerHp = Math.min(data.playerHp ?? playerMaxHp, playerMaxHp);
if (monsters.length === 0) return { win: true, turns: 0, playerHpRemaining: startingPlayerHp };
let drawPile = prepareCombatDrawPile(shuffle(starterDeck, rng), cards);
let discard = [];
const exhaust = [];
let hand = [];
let pHp = PLAYER_HP, pBlock = 0;
let pStr = 0, pDex = 0, pThorns = 0, pWeak = 0, pVuln = 0, pIntangible = 0;
const zeroCostCardIdsThisTurn = new Set();
let pHp = startingPlayerHp, pBlock = data.playerStartBlock || 0;
let pStr = data.playerStrength || 0, pDex = 0, pThorns = data.playerThorns || 0, pWeak = 0, pVuln = 0, pIntangible = 0;
let blockGainMultiplier = 1;
let handCostZeroThisTurn = false;
let drawDisabledThisTurn = false;
@@ -185,6 +246,9 @@ export function simulateCombat(data, rng, stats) {
let cardsDrawnThisCombat = 0;
let bonusRewardScreens = 0;
let activeKillReward = 0;
let comboCount = 0;
let holyChargeCount = 0;
let damagePowerStrengthUsed = false;
let energy = 0;
const powers = [];
const mob = monsters.map((m) => ({
@@ -200,6 +264,24 @@ export function simulateCombat(data, rng, stats) {
if (!alive.length) return null;
return alive[Math.floor(rng() * alive.length)];
};
const randomCardPool = (sourceCard) => Object.entries(cards)
.filter(([, rc]) => {
if (!rc || rc.token === true || rc.curse === true || rc.unplayable === true) return false;
if (sourceCard.addRandomCardKind && rc.kind !== sourceCard.addRandomCardKind) return false;
if (sourceCard.addRandomCardSameClass === true && rc.class !== sourceCard.class) return false;
return true;
})
.map(([id]) => id);
const expectedIncomingDamage = () => mob.filter((m) => m.alive).reduce((total, m) => {
if (!m.intents || m.intents.length === 0) return total;
const expected = m.intents.reduce((sum, intent) => {
if (intent.kind !== 'Attack') return sum;
let amount = calcEnemyAttack(intent.value, m.str, m.weak, pVuln, enemyStrengthLossThisTurn);
if (pIntangible > 0 && amount > 1) amount = 1;
return sum + amount;
}, 0) / m.intents.length;
return total + expected;
}, 0);
const removeEnemyBlock = (target) => {
if (target) target.block = 0;
};
@@ -290,10 +372,26 @@ export function simulateCombat(data, rng, stats) {
if (hand.length >= 10) {
discard.push(card);
triggerSly(card);
} else hand.push(card);
} else {
hand.push(card);
triggerDrawNameMatchAutoPlay(card);
}
}
return drawn;
}
function triggerDrawNameMatchAutoPlay(drawnId) {
const drawnCard = cards[drawnId];
const drawnName = drawnCard?.name || '';
if (!drawnName) return;
for (const pid of powers) {
const pc = cards[pid];
if (!pc?.drawNameMatchAutoPlay || !drawnName.includes(pc.drawNameMatchAutoPlay)) continue;
const idx = hand.indexOf(drawnId);
if (idx < 0) continue;
hand.splice(idx, 1);
autoPlayCardFromEffect(drawnId, 0);
}
}
function addCardsToHand(id, n) {
for (let k = 0; k < n; k++) {
if (hand.length >= 10) discard.push(id);
@@ -308,10 +406,30 @@ export function simulateCombat(data, rng, stats) {
pBlock += amount;
return amount;
}
function smartDiscardIndex() {
if (hand.length === 0) return -1;
if (data.smartPlayer !== true) return hand.length - 1;
const ranked = hand.map((id, index) => {
const card = cards[id] || {};
const isSly = card.sly === true || skillSlyOnPlayCards.has(id) || turnSkillSlyCards.has(id);
const utility = (card.damage || 0) * (card.hits || 1)
+ (card.block || 0)
+ (card.draw || 0) * 4
+ (card.addShiv || 0) * 4
+ (card.poison || 0) * 2;
return { index, isSly, unplayable: card.unplayable === true, tooExpensive: (card.cost || 0) > energy, utility };
});
ranked.sort((a, b) => Number(b.isSly) - Number(a.isSly)
|| Number(b.unplayable) - Number(a.unplayable)
|| Number(b.tooExpensive) - Number(a.tooExpensive)
|| a.utility - b.utility
|| a.index - b.index);
return ranked[0].index;
}
function discardForTurnStart(n) {
const cnt = Math.min(n, hand.length);
for (let i = 0; i < cnt; i++) {
const idx = hand
const idx = data.smartPlayer === true ? smartDiscardIndex() : hand
.map((id, k) => ({ id, k, card: cards[id] }))
.sort((a, b) => {
const ac = a.card?.cost || 0;
@@ -335,8 +453,26 @@ export function simulateCombat(data, rng, stats) {
}
return n;
}
function countOwnedNameMatches(match) {
if (!match) return 0;
let n = 0;
for (const pile of [hand, drawPile, discard, exhaust]) {
for (const id of pile) {
const name = cards[id]?.name || '';
if (name.includes(match)) n++;
}
}
return n;
}
function attackBaseForCard(id, c) {
let base = c.damage || 0;
if (c.damageNameMatch && c.damagePerOwnedNameMatch) {
base += countOwnedNameMatches(c.damageNameMatch) * c.damagePerOwnedNameMatch;
}
if (c.damageFromCurrentBlock) base += pBlock * c.damageFromCurrentBlock;
const comboScale = (c.damagePerCombo || 0) + powerFieldTotal('attackDamagePerCombo');
if (comboScale) base += comboCount * comboScale;
if (c.damagePerHolyCharge) base += holyChargeCount * c.damagePerHolyCharge;
const otherHand = Math.max(0, hand.length - 1);
if (c.damagePerOtherHandCard) base += otherHand * c.damagePerOtherHandCard;
if (c.damagePerAttackPlayedThisTurn) base += turnAttackCardsPlayed * c.damagePerAttackPlayedThisTurn;
@@ -388,6 +524,69 @@ export function simulateCombat(data, rng, stats) {
}
return total;
}
function powerFieldMax(field) {
let best = 0;
for (const pid of powers) best = Math.max(best, cards[pid]?.[field] || 0);
return best;
}
function comboMax() {
return Math.max(5, powerFieldMax('comboMax'));
}
function gainCombo(amount) {
if (amount > 0) comboCount = Math.min(comboMax(), comboCount + amount);
}
function holyChargeMax() {
return Math.max(3, powerFieldMax('holyChargeMax'));
}
function gainHolyCharge(amount) {
if (amount > 0) holyChargeCount = Math.min(holyChargeMax(), holyChargeCount + amount);
}
function triggerExhaust(count = 1) {
const drawOnExhaust = powerFieldTotal('drawOnExhaust');
if (drawOnExhaust > 0 && count > 0) draw(drawOnExhaust * count);
}
function addRandomCardsFromEffect(sourceCard, count) {
if (!count || count <= 0) return [];
const pool = randomCardPool(sourceCard);
if (!pool.length) return [];
const added = [];
for (let i = 0; i < count; i++) {
const id = pool[Math.floor(rng() * pool.length)];
if (!id) continue;
addCardsToHand(id, 1);
if (sourceCard.addedCardsCostZeroThisTurn === true) zeroCostCardIdsThisTurn.add(id);
added.push(id);
}
return added;
}
function exhaustHandNonAttackEffects(c) {
if (c.exhaustHandNonAttack !== true || hand.length === 0) return 0;
let exhaustedCount = 0;
for (let i = hand.length - 1; i >= 0; i--) {
const id = hand[i];
const hc = cards[id];
if (hc?.kind === 'Attack') continue;
hand.splice(i, 1);
exhaust.push(id);
exhaustedCount++;
}
if (exhaustedCount > 0) {
if (c.blockPerExhaustedCard) addBlock(exhaustedCount * c.blockPerExhaustedCard);
triggerExhaust(exhaustedCount);
}
return exhaustedCount;
}
function exhaustHandAllEffects(c) {
if (c.exhaustHandAll !== true || hand.length === 0) return 0;
let exhaustedCount = 0;
while (hand.length > 0) {
const id = hand.pop();
exhaust.push(id);
exhaustedCount++;
}
if (exhaustedCount > 0) triggerExhaust(exhaustedCount);
return exhaustedCount;
}
function resolveCardEffects(id, c, costSpent, recordStats = true) {
const alive = aliveList();
let dmg = 0;
@@ -416,7 +615,7 @@ export function simulateCombat(data, rng, stats) {
}
const xEnergy = costSpent || 0;
if (c.kind === 'Attack') {
if (alive.length && (c.damage || c.xDamagePerEnergy)) {
if (alive.length && (c.damage != null || c.xDamagePerEnergy != null || c.damageFromCurrentBlock != null)) {
const baseDamage = c.xDamagePerEnergy ? xEnergy * c.xDamagePerEnergy : attackBaseForCard(id, c);
const bonusHits = (c.otherHandAtLeast && c.bonusHitsWhenOtherHandAtLeast && Math.max(0, hand.length - 1) >= c.otherHandAtLeast)
? c.bonusHitsWhenOtherHandAtLeast : 0;
@@ -431,8 +630,9 @@ export function simulateCombat(data, rng, stats) {
if (!target || !target.alive) return { killed: false, dealt: 0 };
let dealt = amount;
if (target.vuln > 0) dealt = Math.floor(dealt * 1.5);
if (target.weak > 0 && c.attackDamageVsWeakMultiplier && c.attackDamageVsWeakMultiplier > 1) {
dealt = Math.floor(dealt * c.attackDamageVsWeakMultiplier);
const weakMultiplier = Math.max(c.attackDamageVsWeakMultiplier || 1, powerFieldMax('attackDamageVsWeakMultiplier'));
if (target.weak > 0 && weakMultiplier > 1) {
dealt = Math.floor(dealt * weakMultiplier);
}
if (c.pierce === true) {
target.hp -= dealt;
@@ -448,6 +648,10 @@ export function simulateCombat(data, rng, stats) {
target.alive = false;
killed = true;
if (c.rewardOnKill) bonusRewardScreens += c.rewardOnKill;
if (c.maxHpOnKill) {
playerMaxHp += c.maxHpOnKill;
pHp += c.maxHpOnKill;
}
}
return { killed, dealt };
};
@@ -489,11 +693,11 @@ export function simulateCombat(data, rng, stats) {
roundKilled = resolveAttackRound();
} while (c.repeatOnKill === true && roundKilled === true && countAliveMonsters() > 0);
}
if (c.block) blockGained = addBlock(c.block);
if (c.block) blockGained = addBlock(c.block + holyChargeCount * (c.blockPerHolyCharge || 0));
} else if (c.kind === 'Power') {
if (recordStats) powers.push(id);
powers.push(id);
} else {
if (c.block) blockGained = addBlock(c.block);
if (c.block) blockGained = addBlock(c.block + holyChargeCount * (c.blockPerHolyCharge || 0));
const weakAmount = (c.weak || 0) + (c.xWeakPerEnergy || 0) * xEnergy;
const vulnAmount = c.vuln || 0;
if ((weakAmount || vulnAmount || c.poison || c.removeEnemyBlock || c.removeEnemyArtifact || c.enemyStrengthLossThisTurn) && alive.length) {
@@ -525,8 +729,10 @@ export function simulateCombat(data, rng, stats) {
if (c.dex) pDex += c.dex;
if (c.thorns) pThorns += c.thorns;
if (c.selfVuln) pVuln += c.selfVuln;
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
if (c.heal) pHp = Math.min(pHp + c.heal + holyChargeCount * (c.healPerHolyCharge || 0), playerMaxHp);
if (c.gainEnergy) energy += c.gainEnergy;
if (c.kind !== 'Attack' && c.comboGain) gainCombo(c.comboGain);
if (c.removePlayerDebuffs === true) { pWeak = 0; pVuln = 0; }
activeKillReward = c.rewardOnKill || 0;
if (c.intangible) pIntangible += c.intangible;
queueNextTurnEffects(c);
@@ -543,6 +749,28 @@ export function simulateCombat(data, rng, stats) {
}
}
if (c.addShiv && !c.discard && c.discardAll !== true) addCardsToHand('Shiv', c.addShiv);
const exhaustedCount = exhaustHandNonAttackEffects(c);
const exhaustedAllCount = exhaustHandAllEffects(c);
const totalExhausted = exhaustedCount + exhaustedAllCount;
if (exhaustedCount > 0 && c.blockPerExhaustedCard) {
blockGained += exhaustedCount * c.blockPerExhaustedCard;
}
if (c.drawPerExhausted && totalExhausted > 0) {
draw(totalExhausted * c.drawPerExhausted);
}
if (c.addRandomCardCount) addRandomCardsFromEffect(c, c.addRandomCardCount);
if (c.addRandomCardPerExhausted) {
if (totalExhausted > 0) addRandomCardsFromEffect(c, totalExhausted * c.addRandomCardPerExhausted);
}
const topPlayCount = (c.playTopDrawPileCount || 0) + ((c.playTopDrawPileCountPerEnergy || 0) * xEnergy);
if (topPlayCount > 0) {
for (let i = 0; i < topPlayCount; i++) {
if (drawPile.length <= 0) break;
const topId = drawPile.pop();
if (!topId) break;
autoPlayCardFromEffect(topId, 0);
}
}
if (c.cardPlayedDamage && alive.length) {
const target = chooseTarget(aliveList(), 0);
if (target && target.alive) {
@@ -564,6 +792,28 @@ export function simulateCombat(data, rng, stats) {
}
}
}
if (c.kind === 'Attack') {
gainCombo((c.comboGain || 0) + powerFieldTotal('comboOnAttack'));
const extraDamage = powerFieldTotal('attackPlayedDamage');
if (extraDamage > 0) {
const target = chooseTarget(aliveList(), extraDamage);
if (target) {
const r = applyDamage(target.hp, target.block, extraDamage);
target.hp = r.hp; target.block = r.block;
damageDealtThisTurn += extraDamage;
if (target.hp <= 0) target.alive = false;
}
}
const attackWeak = powerFieldTotal('attackWeak');
if (attackWeak > 0) {
const target = chooseTarget(aliveList(), 0);
if (target) applyMonsterWeak(target, attackWeak);
}
}
let holyGain = c.holyChargeGain || 0;
if (c.holyForce === true) holyGain += powerFieldTotal('holyChargeOnHolyForce');
gainHolyCharge(holyGain);
if (c.holyChargeSpendAll === true) holyChargeCount = 0;
if (c.blockPerDamageDealtThisTurn && c.blockPerDamageDealtThisTurn > 0 && c.kind !== 'Power') {
blockGained += addBlock(Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn));
}
@@ -588,13 +838,54 @@ export function simulateCombat(data, rng, stats) {
while (hand.length) { discardHandCard(hand.length - 1, true); discarded++; }
} else if (c.discard) {
const n = Math.min(c.discard, hand.length);
for (let i = 0; i < n; i++) { discardHandCard(hand.length - 1, true); discarded++; }
for (let i = 0; i < n; i++) { discardHandCard(smartDiscardIndex(), true); discarded++; }
}
if (c.addShiv && (c.discard || c.discardAll === true)) addCardsToHand('Shiv', c.addShiv);
if (c.addShivPerDiscard === true) addCardsToHand('Shiv', discarded);
if (c.drawPerDiscarded) draw(discarded * c.drawPerDiscarded);
}
function autoPlayCardFromEffect(id, energySpent = 0) {
const c = cards[id];
if (!c) return false;
const skillFree = c.kind === 'Skill' && c.useAllEnergy !== true && nextSkillCostZero === true;
const skillRepeat = c.kind === 'Skill' && (nextSkillRepeatCount || 0) > 0 ? nextSkillRepeatCount : 0;
activeKillReward = c.rewardOnKill || 0;
resolveCardEffects(id, c, energySpent, false);
const playedBlock = powerFieldTotal('cardPlayedBlock');
if (playedBlock > 0) addBlock(playedBlock);
if (c.cardPlayedDamage && aliveList().length) {
const target = chooseTarget(aliveList(), 0);
if (target && target.alive) {
target.hp -= c.cardPlayedDamage;
damageDealtThisTurn += c.cardPlayedDamage;
if (target.hp <= 0) target.alive = false;
}
}
if (c.cardPlayedRandomDamage && aliveList().length) {
const target = randomAliveMonster();
if (target) {
target.hp -= c.cardPlayedRandomDamage;
damageDealtThisTurn += c.cardPlayedRandomDamage;
if (target.hp <= 0) target.alive = false;
}
}
if (skillRepeat > 0) {
nextSkillRepeatCount = Math.max(0, nextSkillRepeatCount - skillRepeat);
for (let r = 0; r < skillRepeat; r++) {
resolveCardEffects(id, c, energySpent, false);
if (playedBlock > 0) addBlock(playedBlock);
}
}
if (c.kind === 'Attack') turnAttackCardsPlayed++;
if (skillFree === true && c.nextSkillCostZero !== true) nextSkillCostZero = false;
if (c.exhaust === true || String(c.desc || '').includes('소멸.')) {
exhaust.push(id);
triggerExhaust(1);
} else if (c.kind !== 'Power') {
discard.push(id);
}
return true;
}
while (turns < MAX_TURNS) {
turns++;
turnAttackCardsPlayed = 0;
@@ -610,7 +901,9 @@ export function simulateCombat(data, rng, stats) {
drawDisabledThisTurn = false;
skillCostReductionThisTurn = 0;
// 파워 발동 — Lua StartPlayerTurn 동기화 (블록 리셋 후 strength/energy/block 파워)
zeroCostCardIdsThisTurn.clear();
if (nextTurnKeepBlock === true) nextTurnKeepBlock = false;
else if (powers.some((pid) => cards[pid]?.powerEffect === 'keepBlock')) {}
else pBlock = 0;
turnAttackMultiplier = nextTurnAttackMultiplier;
nextTurnAttackMultiplier = 1;
@@ -623,6 +916,7 @@ export function simulateCombat(data, rng, stats) {
if (pc.powerEffect === 'strengthPerTurn') pStr += pc.value;
else if (pc.powerEffect === 'energyPerTurn') energyBonus += pc.value;
else if (pc.powerEffect === 'blockPerTurn') pBlock += pc.value;
else if (pc.powerEffect === 'keepBlock') {}
else if (pc.powerEffect === 'poisonPerTurn') {
for (const m of mob) if (m.alive) applyPoisonToMonster(m, pc.value);
} else if (pc.powerEffect === 'damagePerTurn') {
@@ -632,6 +926,8 @@ export function simulateCombat(data, rng, stats) {
m.hp = r.hp; m.block = r.block;
if (m.hp <= 0) m.alive = false;
}
} else if (pc.powerEffect === 'healPerTurn') {
pHp = Math.min(playerMaxHp, pHp + (pc.value || 0));
}
if (pc.turnStartShiv) addCardsToHand('Shiv', pc.turnStartShiv);
if (pc.turnStartDraw) powerTurnDraw += pc.turnStartDraw;
@@ -642,15 +938,29 @@ export function simulateCombat(data, rng, stats) {
for (const entry of nextTurnAddCards) addCardsToHand(entry.cardId, entry.amount);
nextTurnAddCards = [];
}
energy = ENERGY + energyBonus;
energy = ENERGY + (data.energyBonus || 0) + energyBonus;
const drawBonus = nextTurnDraw + powerTurnDraw;
nextTurnDraw = 0;
draw(HAND_SIZE + drawBonus);
draw(HAND_SIZE + drawBonus + (turns === 1 ? (data.openingDrawBonus || 0) : 0));
if (powerTurnDiscard > 0) discardForTurnStart(powerTurnDiscard);
while (true) {
const alive = aliveList();
if (alive.length === 0) break;
const idx = chooseAction(hand, cards, energy, { drawPileCount: drawPile.length, nextSkillCostZero, skillCostReductionThisTurn, handCostZeroThisTurn, combatCardCostReduction });
const idx = chooseAction(hand, cards, energy, {
drawPileCount: drawPile.length,
nextSkillCostZero,
skillCostReductionThisTurn,
handCostZeroThisTurn,
combatCardCostReduction,
zeroCostCardIdsThisTurn,
incomingDamage: data.smartPlayer === true ? expectedIncomingDamage() : 0,
currentBlock: pBlock,
turnAttackCardsPlayed,
cardsDrawnThisCombat,
drawPileCards: drawPile,
discardCards: discard,
exhaustCards: exhaust,
});
if (idx < 0) break;
const id = hand[idx], c = cards[id];
let dmg = 0;
@@ -661,7 +971,11 @@ export function simulateCombat(data, rng, stats) {
const cost = handCostZeroThisTurn === true ? 0 : (c.useAllEnergy === true ? energy : (skillFree ? 0 : (c.kind === 'Skill' ? Math.max(0, baseCost - skillCostReductionThisTurn) : baseCost)));
const finalCost = c.useAllEnergy === true ? cost : Math.max(0, cost - combatReduction);
energy -= finalCost;
hand.splice(idx, 1);
resolveCardEffects(id, c, finalCost);
if (c.kind === 'Attack' && (data.healOnAttack || 0) > 0) {
pHp = Math.min(playerMaxHp, pHp + data.healOnAttack);
}
const playedBlock = powerFieldTotal('cardPlayedBlock');
if (playedBlock > 0) addBlock(playedBlock);
if (skillRepeat > 0) {
@@ -673,9 +987,11 @@ export function simulateCombat(data, rng, stats) {
}
if (c.kind === 'Attack') turnAttackCardsPlayed++;
if (skillFree === true && c.nextSkillCostZero !== true) nextSkillCostZero = false;
hand.splice(idx, 1);
queueSelectedReserve(c);
if (c.exhaust === true || String(c.desc || '').includes('소멸.')) exhaust.push(id);
if (c.exhaust === true || String(c.desc || '').includes('소멸.')) {
exhaust.push(id);
triggerExhaust(1);
}
else if (c.kind !== 'Power') discard.push(id);
if (c.combatCostReductionOnPlay && c.combatCostReductionOnPlay > 0) {
combatCardCostReduction[id] = (combatCardCostReduction[id] || 0) + c.combatCostReductionOnPlay;
@@ -725,8 +1041,18 @@ export function simulateCombat(data, rng, stats) {
const atk = calcEnemyAttack(it.value, m.str, m.weak, pVuln, enemyStrengthLossThisTurn);
const beforeHp = pHp;
let incoming = atk;
const reduction = Math.min(0.75, powerFieldTotal('damageTakenReduction'));
if (reduction > 0) incoming = Math.floor(incoming * (1 - reduction));
if (pIntangible > 0 && incoming > 1) incoming = 1;
const r = applyDamage(pHp, pBlock, incoming); pHp = r.hp; pBlock = r.block;
if (beforeHp > pHp) {
const reactiveBlock = powerFieldTotal('blockOnDamaged');
if (reactiveBlock > 0) addBlock(reactiveBlock);
if (!damagePowerStrengthUsed) {
const reactiveStrength = powerFieldTotal('strengthOnDamagedOnce');
if (reactiveStrength > 0) { pStr += reactiveStrength; damagePowerStrengthUsed = true; }
}
}
if (beforeHp > pHp && pThorns > 0) {
m.hp -= pThorns;
if (m.hp <= 0) m.alive = false;

View File

@@ -121,6 +121,14 @@ test('chooseAction: 공격 없으면 스킬 선택', () => {
assert.equal(idx, 0);
});
test('chooseAction: 예상 피해가 남으면 방어 카드를 우선 선택', () => {
const cards = {
Hit: { kind: 'Attack', cost: 1, damage: 12 },
Guard: { kind: 'Skill', cost: 1, block: 8 },
};
assert.equal(chooseAction(['Hit', 'Guard'], cards, 1, { incomingDamage: 8, currentBlock: 0 }), 1);
});
test('chooseAction: 사용 가능 카드 없으면 -1', () => {
const idx = chooseAction(['Bash'], CARDS, 1);
assert.equal(idx, -1);
@@ -220,6 +228,21 @@ test('simulateCombat: 복합 카드(공격+방어) 블록이 적 공격을 흡
assert.equal(r.playerHpRemaining, 80);
});
test('simulateCombat: 캠페인 시작 체력과 유물 전투 보너스를 반영', () => {
const data = {
cards: { Guard: { name: 'Guard', cost: 1, kind: 'Skill', block: 1 } },
starterDeck: ['Guard'],
monsters: [{ name: 'Dummy', maxHp: 1, intents: [{ kind: 'Attack', value: 1 }] }],
playerHp: 37,
playerMaxHp: 70,
playerStartBlock: 6,
energyBonus: 1,
openingDrawBonus: 2,
};
const result = simulateCombat(data, mulberry32(3));
assert.ok(result.playerHpRemaining <= 37);
});
test('calcAttack: 힘·약화·취약 공식 (Lua CalcPlayerAttack·DealDamageToTarget 동기화)', () => {
assert.equal(calcAttack(6, 0, 0, 0), 6); // 기본
assert.equal(calcAttack(6, 2, 0, 0), 8); // 힘+2
@@ -626,9 +649,10 @@ test("simulateCombat: damagePerAttackPlayedThisTurn scales Finisher", () => {
starterDeck: ["Hit", "Finisher"],
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0);
const stats = {};
const r = simulateCombat(data, () => 0, stats);
assert.equal(r.win, true);
assert.equal(r.turns, 2);
assert.ok((stats.Finisher?.damage || 0) >= 6);
});
test("simulateCombat: damagePerOtherHandCard and damagePerSkillInHand are applied", () => {
@@ -643,9 +667,11 @@ test("simulateCombat: damagePerOtherHandCard and damagePerSkillInHand are applie
starterDeck: ["Skill1", "Skill2", "Blank", "Precise", "Flechettes"],
monsters: [{ name: "Dummy", maxHp: 21, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0);
const stats = {};
const r = simulateCombat(data, () => 0, stats);
assert.equal(r.win, true);
assert.equal(r.turns, 5);
assert.ok((stats.Precise?.damage || 0) >= 5);
assert.ok((stats.Flechettes?.damage || 0) >= 10);
});
test("simulateCombat: damagePerDiscardedThisTurn and bonusHitsWhenOtherHandAtLeast work", () => {
@@ -1076,6 +1102,90 @@ test("simulateCombat: blockPerDamageDealtThisTurn grants block from damage dealt
assert.equal(r.win, true);
});
test("simulateCombat: damageFromCurrentBlock uses current block as attack damage", () => {
const data = {
cards: {
Guard: { name: "Guard", cost: 1, kind: "Skill", block: 5 },
BodySlam: { name: "BodySlam", cost: 1, kind: "Attack", damageFromCurrentBlock: 1 },
},
starterDeck: ["Guard", "BodySlam"],
monsters: [{ name: "Dummy", maxHp: 5, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.equal(r.turns, 1);
});
test("simulateCombat: damagePerOwnedNameMatch counts matching owned cards across combat piles", () => {
const data = {
cards: {
Strike1: { name: "타격", cost: 99, kind: "Attack", damage: 0 },
Strike2: { name: "타격", cost: 99, kind: "Attack", damage: 0 },
Perfected: { name: "완벽한 타격", cost: 0, kind: "Attack", damage: 6, damageNameMatch: "타격", damagePerOwnedNameMatch: 2 },
},
starterDeck: ["Strike1", "Strike2", "Perfected"],
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
};
const stats = {};
const r = simulateCombat(data, () => 0.999999, stats);
assert.equal(r.win, true);
assert.ok((stats.Perfected?.damage || 0) >= 12);
});
test("simulateCombat: exhaustHandNonAttack exhausts only non-attacks and grants block per exhausted card", () => {
const data = {
cards: {
SecondWind: { name: "기사회생", cost: 0, kind: "Skill", exhaustHandNonAttack: true, blockPerExhaustedCard: 5 },
Guard1: { name: "수비1", cost: 99, kind: "Skill", block: 0 },
Guard2: { name: "수비2", cost: 99, kind: "Skill", block: 0 },
Hit: { name: "타격", cost: 99, kind: "Attack", damage: 1 },
},
starterDeck: ["Guard1", "Guard2", "Hit", "SecondWind"],
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 10 }] }],
};
const stats = {};
simulateCombat(data, () => 0.999999, stats);
assert.ok((stats.SecondWind?.block || 0) >= 10);
});
test("simulateCombat: drawOnExhaust draws when cards are exhausted", () => {
const data = {
cards: {
Embrace: { name: "어둠의 포옹", cost: 0, kind: "Power", drawOnExhaust: 1 },
Burn: { name: "소각", cost: 0, kind: "Skill", exhaust: true },
Hit: { name: "타격", cost: 0, kind: "Attack", damage: 6 },
},
starterDeck: ["Embrace", "Burn", "Hit"],
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.equal(r.turns, 1);
});
test("simulateCombat: keepBlock power preserves block across turns", () => {
const data = {
cards: {
Barricade: { name: "바리케이드", cost: 0, kind: "Power", powerEffect: "keepBlock", value: 0 },
Guard: { name: "수비", cost: 0, kind: "Skill", block: 5 },
Pass: { name: "대기", cost: 99, kind: "Skill", block: 0 },
},
starterDeck: ["Barricade", "Guard", "Pass"],
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 3 }, { kind: "Attack", value: 3 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.draw, true);
assert.equal(r.playerHpRemaining, 80);
});
test("chooseAction: damageFromCurrentBlock values attack using current block", () => {
const cards = {
Guard: { name: "Guard", cost: 1, kind: "Skill", block: 5 },
BodySlam: { name: "BodySlam", cost: 1, kind: "Attack", damageFromCurrentBlock: 1 },
};
assert.equal(chooseAction(["BodySlam", "Guard"], cards, 1, { currentBlock: 6 }), 0);
});
test("simulateCombat: cardPlayedRandomDamage hits a random enemy on card play", () => {
const data = {
cards: {
@@ -1101,6 +1211,95 @@ test("simulateCombat: rewardOnKill grants an extra reward screen when an attack
assert.equal(r.bonusRewardScreens, 1);
});
test("simulateCombat: maxHpOnKill increases max hp and heals when attack kills", () => {
const data = {
cards: {
Feed: { name: "포식", cost: 1, kind: "Attack", damage: 10, maxHpOnKill: 3 },
},
starterDeck: ["Feed"],
monsters: [{ name: "Dummy", maxHp: 10, intents: [{ kind: "Attack", value: 0 }] }],
playerHp: 50,
playerMaxHp: 80,
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.equal(r.playerHpRemaining, 53);
});
test("simulateCombat: drawNameMatchAutoPlay auto-plays matching drawn cards", () => {
const data = {
cards: {
Hellraiser: { name: "지옥검무", cost: 0, kind: "Power", drawNameMatchAutoPlay: "타격" },
Strike: { name: "강타격", cost: 99, kind: "Attack", damage: 9 },
Pass1: { name: "대기1", cost: 99, kind: "Skill" },
Pass2: { name: "대기2", cost: 99, kind: "Skill" },
Pass3: { name: "대기3", cost: 99, kind: "Skill" },
Pass4: { name: "대기4", cost: 99, kind: "Skill" },
},
starterDeck: ["Hellraiser", "Pass1", "Pass2", "Pass3", "Pass4", "Strike"],
monsters: [{ name: "Dummy", maxHp: 9, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
});
test("simulateCombat: addRandomCardCount can add same-class attack as zero-cost this turn", () => {
const data = {
cards: {
InfernalBlade: {
name: "지옥검",
cost: 0,
kind: "Skill",
addRandomCardCount: 1,
addRandomCardKind: "Attack",
addRandomCardSameClass: true,
addedCardsCostZeroThisTurn: true,
class: "warrior",
},
BigHit: { name: "큰 일격", cost: 2, kind: "Attack", damage: 12, class: "warrior" },
OffClass: { name: "외부 공격", cost: 0, kind: "Attack", damage: 1, class: "rogue" },
},
starterDeck: ["InfernalBlade"],
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
});
test("simulateCombat: drawPerExhausted draws for each exhausted card", () => {
const data = {
cards: {
Stoke: { name: "화력 증폭", cost: 0, kind: "Skill", exhaustHandAll: true, drawPerExhausted: 1 },
Filler1: { name: "채우기1", cost: 99, kind: "Skill" },
Filler2: { name: "채우기2", cost: 99, kind: "Skill" },
Hit: { name: "일격", cost: 0, kind: "Attack", damage: 8 },
},
starterDeck: ["Stoke", "Filler1", "Filler2", "Hit"],
monsters: [{ name: "Dummy", maxHp: 8, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
});
test("simulateCombat: playTopDrawPileCountPerEnergy auto-plays top draw pile cards", () => {
const data = {
cards: {
Cascade: { name: "연쇄", cost: 0, kind: "Skill", useAllEnergy: true, playTopDrawPileCountPerEnergy: 1, innate: true },
Filler1: { name: "준비1", cost: 99, kind: "Skill", innate: true },
Filler2: { name: "준비2", cost: 99, kind: "Skill", innate: true },
Filler3: { name: "준비3", cost: 99, kind: "Skill", innate: true },
Filler4: { name: "준비4", cost: 99, kind: "Skill", innate: true },
Hit1: { name: "타격1", cost: 99, kind: "Attack", damage: 6 },
Hit2: { name: "타격2", cost: 99, kind: "Attack", damage: 6 },
Hit3: { name: "타격3", cost: 99, kind: "Attack", damage: 6 },
},
starterDeck: ["Cascade", "Filler1", "Filler2", "Filler3", "Filler4", "Hit1", "Hit2", "Hit3"],
monsters: [{ name: "Dummy", maxHp: 18, intents: [{ kind: "Attack", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
});
test("simulateCombat: intangible cards reduce incoming damage and persist across turns", () => {
const data = {
cards: {
@@ -1192,3 +1391,102 @@ test("simulateCombat: shivAoe makes Shivs hit all enemies", () => {
assert.equal(r.win, true);
assert.equal(r.turns, 1);
});
test("simulateCombat: comboGain and damagePerCombo scale repeated attacks", () => {
const data = {
cards: {
Brandish: { name: "브랜디쉬", cost: 1, kind: "Attack", damage: 2, hits: 2, comboGain: 1, damagePerCombo: 1 },
},
starterDeck: ["Brandish"],
monsters: [{ name: "Dummy", maxHp: 14, intents: [{ kind: "Attack", value: 0 }] }],
};
const stats = {};
const r = simulateCombat(data, () => 0.999999, stats);
assert.equal(r.win, true);
assert.ok(stats.Brandish.damage > stats.Brandish.plays * 4);
});
test("simulateCombat: comboMax power raises the combo cap", () => {
const data = {
cards: {
ComboSynergy: { name: "콤보 시너지", cost: 0, kind: "Power", comboMax: 8, comboOnAttack: 2, attackDamagePerCombo: 1, innate: true },
Hit: { name: "연속 베기", cost: 0, kind: "Attack", damage: 1 },
},
starterDeck: ["ComboSynergy", "Hit"],
monsters: [{ name: "Dummy", maxHp: 40, intents: [{ kind: "Attack", value: 0 }] }],
};
const stats = {};
const r = simulateCombat(data, () => 0.999999, stats);
assert.equal(r.win, true);
assert.ok(stats.Hit.damage > stats.Hit.plays);
});
test("simulateCombat: healPerTurn power restores hp at turn start", () => {
const data = {
cards: {
Recovery: { name: "셀프 리커버리", cost: 0, kind: "Power", powerEffect: "healPerTurn", value: 3 },
Hit: { name: "마무리", cost: 1, kind: "Attack", damage: 1 },
},
starterDeck: ["Recovery", "Hit"],
monsters: [{ name: "Dummy", maxHp: 2, intents: [{ kind: "Attack", value: 2 }] }],
playerHp: 70,
playerMaxHp: 80,
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.ok(r.playerHpRemaining >= 69);
});
test("simulateCombat: Holy Charge scales repeated Holy Force attacks", () => {
const data = {
cards: {
ChargeStrike: { name: "차지 타격", cost: 1, kind: "Attack", damage: 2, holyChargeGain: 1, damagePerHolyCharge: 1 },
},
starterDeck: ["ChargeStrike"],
monsters: [{ name: "Dummy", maxHp: 14, intents: [{ kind: "Attack", value: 0 }] }],
};
const stats = {};
const r = simulateCombat(data, () => 0.999999, stats);
assert.equal(r.win, true);
assert.ok(stats.ChargeStrike.damage > stats.ChargeStrike.plays * 2);
});
test("simulateCombat: damageTakenReduction lowers incoming HP damage", () => {
const data = {
cards: {
Achilles: { name: "아킬레스", cost: 3, kind: "Power", damageTakenReduction: 0.25, innate: true },
Wait1: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Wait2: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Wait3: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Wait4: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Finish: { name: "마무리", cost: 3, kind: "Power", cardPlayedDamage: 2 },
},
starterDeck: ["Finish", "Achilles", "Wait1", "Wait2", "Wait3", "Wait4"],
monsters: [{ name: "Dummy", maxHp: 2, intents: [{ kind: "Attack", value: 10 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.equal(r.playerHpRemaining, 73);
});
test("simulateCombat: blockOnDamaged protects against later attackers", () => {
const data = {
cards: {
Armor: { name: "블레싱 아머", cost: 3, kind: "Power", blockOnDamaged: 6, strengthOnDamagedOnce: 2, innate: true },
Wait1: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Wait2: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Wait3: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Wait4: { name: "대기", cost: 99, kind: "Status", unplayable: true, innate: true },
Finish1: { name: "마무리", cost: 3, kind: "Power", cardPlayedDamage: 1 },
Finish2: { name: "마무리", cost: 3, kind: "Power", cardPlayedDamage: 1 },
},
starterDeck: ["Finish1", "Finish2", "Armor", "Wait1", "Wait2", "Wait3", "Wait4"],
monsters: [
{ name: "A", maxHp: 1, intents: [{ kind: "Attack", value: 5 }] },
{ name: "B", maxHp: 1, intents: [{ kind: "Attack", value: 5 }] },
],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.equal(r.playerHpRemaining, 70);
});

View File

@@ -49,6 +49,8 @@ local skillFree = false
local skillRepeat = 0
if self.HandCostZeroThisTurn == true then
cost = 0
elseif self.ZeroCostCardIdsThisTurn ~= nil and self.ZeroCostCardIdsThisTurn[cardId] == true then
cost = 0
elseif c.useAllEnergy == true then
cost = self.Energy
end
@@ -71,6 +73,8 @@ if self.Energy < cost then
end
self.Energy = self.Energy - cost
self.ActiveKillReward = c.rewardOnKill or 0
self.ActiveKillMaxHpGain = c.maxHpOnKill or 0
table.remove(self.Hand, slot)
self:ResolveCardEffects(cardId, slot, c, false, cost)
local function applyCardPlayHooks()
if self:HasPowerField("cardPlayedBlock") == true then
@@ -82,6 +86,8 @@ local function applyCardPlayHooks()
if c.cardPlayedRandomDamage ~= nil and c.cardPlayedRandomDamage > 0 then
self:DealDirectDamageToRandomMonster(c.cardPlayedRandomDamage)
end
self:ApplyAttackCardPlayHooks(c)
self:ApplyHolyForceCardPlayHooks(c)
end
applyCardPlayHooks()
if skillRepeat > 0 then
@@ -106,16 +112,19 @@ end
if self.ActiveKillReward ~= nil and self.ActiveKillReward <= 0 then
self.ActiveKillReward = 0
end
if self.ActiveKillMaxHpGain ~= nil and self.ActiveKillMaxHpGain <= 0 then
self.ActiveKillMaxHpGain = 0
end
if c.combatCostReductionOnPlay ~= nil and c.combatCostReductionOnPlay > 0 then
if self.CombatCardCostReduction == nil then
self.CombatCardCostReduction = {}
end
self.CombatCardCostReduction[cardId] = (self.CombatCardCostReduction[cardId] or 0) + c.combatCostReductionOnPlay
end
table.remove(self.Hand, slot)
if c.exhaust == true then
if self.ExhaustPile == nil then self.ExhaustPile = {} end
table.insert(self.ExhaustPile, cardId)
self:TriggerExhaustEffects(1)
elseif c.kind ~= "Power" then
table.insert(self.DiscardPile, cardId)
end
@@ -300,6 +309,13 @@ local killed = false
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
if self.ActiveKillReward ~= nil and self.ActiveKillReward > 0 then
self.BonusRewardScreens = (self.BonusRewardScreens or 0) + self.ActiveKillReward
end
if self.ActiveKillMaxHpGain ~= nil and self.ActiveKillMaxHpGain > 0 then
self.PlayerMaxHp = self.PlayerMaxHp + self.ActiveKillMaxHpGain
self.PlayerHp = self.PlayerHp + self.ActiveKillMaxHpGain
end
killed = true
end
return killed`, [
@@ -411,6 +427,11 @@ end
if killCount > 0 and self.ActiveKillReward ~= nil and self.ActiveKillReward > 0 then
self.BonusRewardScreens = (self.BonusRewardScreens or 0) + (killCount * self.ActiveKillReward)
end
if killCount > 0 and self.ActiveKillMaxHpGain ~= nil and self.ActiveKillMaxHpGain > 0 then
local gain = killCount * self.ActiveKillMaxHpGain
self.PlayerMaxHp = self.PlayerMaxHp + gain
self.PlayerHp = self.PlayerHp + gain
end
self:RenderCombat()
self:CheckCombatEnd()
return killCount > 0`, [
@@ -450,10 +471,8 @@ _TimerService:SetTimerOnce(function()
shown = math.floor(shown * self.ActiveAttackDamageVsWeakMultiplier)
end
local killed = self:DealDamageToTarget(damage, pierce)
if killed == true and self.ActiveKillReward ~= nil and self.ActiveKillReward > 0 then
self.BonusRewardScreens = (self.BonusRewardScreens or 0) + self.ActiveKillReward
end
self.ActiveKillReward = 0
self.ActiveKillMaxHpGain = 0
self.ActiveAttackDamageVsWeakMultiplier = 1
self:ShowDmgPop(targetIndex, shown)
self:RenderCombat()
@@ -510,10 +529,8 @@ _TimerService:SetTimerOnce(function()
end
end
end
if killCount > 0 and self.ActiveKillReward ~= nil and self.ActiveKillReward > 0 then
self.BonusRewardScreens = (self.BonusRewardScreens or 0) + (killCount * self.ActiveKillReward)
end
self.ActiveKillReward = 0
self.ActiveKillMaxHpGain = 0
self.ActiveAttackDamageVsWeakMultiplier = 1
self:RenderCombat()
self:CheckCombatEnd()
@@ -535,6 +552,11 @@ for i = 1, #self.Monsters do
if self.Monsters[i].alive == true then self.TargetIndex = i; break end
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('DealDamageToPlayer', `local dmg = amount
local reduction = self:AddPowerFieldTotal("damageTakenReduction")
if reduction ~= nil and reduction > 0 then
reduction = math.min(0.75, reduction)
dmg = math.floor(dmg * (1 - reduction))
end
if self.PlayerBlock > 0 then
local absorbed = math.min(self.PlayerBlock, dmg)
self.PlayerBlock = self.PlayerBlock - absorbed
@@ -545,6 +567,17 @@ if dmg > 0 and self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 and dm
end
if dmg > 0 then
self.PlayerHp = self.PlayerHp - dmg
local reactiveBlock = self:AddPowerFieldTotal("blockOnDamaged")
if reactiveBlock ~= nil and reactiveBlock > 0 then
self:AddCardBlock(reactiveBlock)
end
if self.DamagePowerStrengthUsed ~= true then
local reactiveStrength = self:AddPowerFieldTotal("strengthOnDamagedOnce")
if reactiveStrength ~= nil and reactiveStrength > 0 then
self.PlayerStr = self.PlayerStr + reactiveStrength
self.DamagePowerStrengthUsed = true
end
end
local reflect = self.PlayerThorns or 0
if self:HasRelic("bronzeScales") then
reflect = reflect + 3

View File

@@ -243,6 +243,7 @@ self.BlockGainMultiplier = 1
self:ApplyRelics("turnStart")
if self.NextTurnKeepBlock == true then
self.NextTurnKeepBlock = false
elseif self:HasPowerEffect("keepBlock") == true then
else
self.PlayerBlock = 0
end
@@ -258,6 +259,7 @@ self.ActiveAttackDamageVsWeakMultiplier = 1
self.DrawDamageThisTurn = 0
self.DrawPoisonThisTurn = 0
self.ShivAoeThisCombat = false
self.ZeroCostCardIdsThisTurn = {}
self.SkillSlyOnPlayCards = self.SkillSlyOnPlayCards or {}
self.TurnSkillSlyCards = {}
self.EnemyStrengthLossThisTurn = 0
@@ -275,6 +277,7 @@ if self.PlayerPowers ~= nil then
self.Energy = self.Energy + pc.value
elseif pc.powerEffect == "blockPerTurn" then
self.PlayerBlock = self.PlayerBlock + pc.value
elseif pc.powerEffect == "keepBlock" then
elseif pc.powerEffect == "poisonPerTurn" then
if self.Monsters ~= nil then
for j = 1, #self.Monsters do
@@ -288,6 +291,8 @@ if self.PlayerPowers ~= nil then
if self.Monsters ~= nil then
self:PlayAoeFx(pc.fx or pc.image, pc.value or 0)
end
elseif pc.powerEffect == "healPerTurn" then
self.PlayerHp = math.min(self.PlayerMaxHp, self.PlayerHp + pc.value)
end
if pc.turnStartShiv ~= nil then
self:AddCardsToHand("Shiv", pc.turnStartShiv)
@@ -481,8 +486,11 @@ for i = 1, amount do
\t\tself:TriggerSly(cardId)
\telse
\t\ttable.insert(self.Hand, cardId)
\t\tdrewAny = true
\t\ttable.insert(drawnSlots, #self.Hand)
\t\tlocal autoPlayed = self:TriggerDrawnCardAutoPlay(cardId)
\t\tif autoPlayed ~= true then
\t\t\tdrewAny = true
\t\t\ttable.insert(drawnSlots, #self.Hand)
\t\tend
\tend
end
self:RenderPiles()
@@ -495,8 +503,9 @@ if animate == true and #drawnSlots > 0 then
\t\tlocal slot = drawnSlots[i]
\t\tself:AnimateCardFrom(slot, drawStart, Vector2(self:GetHandSlotX(slot), 0), 0.08 + i * 0.045)
\tend
end
return drawnCards
end`, [
`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' },
], 0, 'any'),

View File

@@ -326,7 +326,104 @@ for i = 1, #self.Hand do
end
end
return n`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }], 0, 'number'),
method('CountOwnedNameMatches', `if match == nil or match == "" then
return 0
end
local n = 0
local function countPile(pile)
if pile == nil then return end
for i = 1, #pile do
local c2 = self.Cards[pile[i]]
local name = ""
if c2 ~= nil and c2.name ~= nil then name = c2.name end
if string.find(name, match, 1, true) ~= nil then
n = n + 1
end
end
end
countPile(self.Hand)
countPile(self.DrawPile)
countPile(self.DiscardPile)
countPile(self.ExhaustPile)
return n`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'match' }], 0, 'number'),
method('MaxPowerField', `local best = 0
if self.PlayerPowers == nil then
return best
end
for i = 1, #self.PlayerPowers do
local powerCard = self.Cards[self.PlayerPowers[i]]
if powerCard ~= nil and powerCard[field] ~= nil and powerCard[field] > best then
best = powerCard[field]
end
end
return best`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'field' }], 0, 'number'),
method('GetComboMax', `local comboMax = 5
local powerMax = self:MaxPowerField("comboMax")
if powerMax ~= nil and powerMax > comboMax then
comboMax = powerMax
end
return comboMax`, [], 0, 'number'),
method('GainCombo', `if amount == nil or amount <= 0 then
return
end
self.ComboCount = math.min(self:GetComboMax(), (self.ComboCount or 0) + amount)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
method('GetHolyChargeMax', `local chargeMax = 3
local powerMax = self:MaxPowerField("holyChargeMax")
if powerMax ~= nil and powerMax > chargeMax then
chargeMax = powerMax
end
return chargeMax`, [], 0, 'number'),
method('GainHolyCharge', `if amount == nil or amount <= 0 then
return
end
self.HolyChargeCount = math.min(self:GetHolyChargeMax(), (self.HolyChargeCount or 0) + amount)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
method('ApplyHolyForceCardPlayHooks', `if c == nil then
return
end
local gain = c.holyChargeGain or 0
if c.holyForce == true then
gain = gain + self:AddPowerFieldTotal("holyChargeOnHolyForce")
end
self:GainHolyCharge(gain)
if c.holyChargeSpendAll == true then
self.HolyChargeCount = 0
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }]),
method('ApplyAttackCardPlayHooks', `if c == nil or c.kind ~= "Attack" then
return
end
local comboGain = c.comboGain or 0
comboGain = comboGain + self:AddPowerFieldTotal("comboOnAttack")
self:GainCombo(comboGain)
local extraDamage = self:AddPowerFieldTotal("attackPlayedDamage")
if extraDamage ~= nil and extraDamage > 0 then
self:DealDirectDamageToTarget(extraDamage)
end
local weakAmount = self:AddPowerFieldTotal("attackWeak")
if weakAmount ~= nil and weakAmount > 0 and self.Monsters ~= nil then
local target = self.Monsters[self.TargetIndex]
if target ~= nil and target.alive == true then
if target.artifact ~= nil and target.artifact > 0 then
target.artifact = target.artifact - 1
else
target.weak = (target.weak or 0) + weakAmount
end
end
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }]),
method('AttackBaseForCard', `local base2 = c.damage or 0
if c.damageNameMatch ~= nil and c.damagePerOwnedNameMatch ~= nil then
base2 = base2 + self:CountOwnedNameMatches(c.damageNameMatch) * c.damagePerOwnedNameMatch
end
if c.damageFromCurrentBlock ~= nil and c.damageFromCurrentBlock ~= 0 then
base2 = base2 + (self.PlayerBlock or 0) * c.damageFromCurrentBlock
end
local comboScale = c.damagePerCombo or 0
comboScale = comboScale + self:AddPowerFieldTotal("attackDamagePerCombo")
if comboScale ~= 0 then
base2 = base2 + (self.ComboCount or 0) * comboScale
end
if c.damagePerHolyCharge ~= nil and c.damagePerHolyCharge ~= 0 then
base2 = base2 + (self.HolyChargeCount or 0) * c.damagePerHolyCharge
end
local otherHand = 0
if self.Hand ~= nil then
otherHand = #self.Hand - 1
@@ -365,6 +462,200 @@ return base2`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
], 0, 'number'),
method('TriggerExhaustEffects', `if count == nil or count <= 0 then
return
end
local drawOnExhaust = self:AddPowerFieldTotal("drawOnExhaust")
if drawOnExhaust ~= nil and drawOnExhaust > 0 then
self:DrawCards(drawOnExhaust * count, true)
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'count' }]),
method('MarkCardCostZeroThisTurn', `if cardId == nil or cardId == "" then
return
end
if self.ZeroCostCardIdsThisTurn == nil then
self.ZeroCostCardIdsThisTurn = {}
end
self.ZeroCostCardIdsThisTurn[cardId] = true`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }]),
method('AutoPlayCardId', `local c = self.Cards[cardId]
if c == nil then
return false
end
local spent = energySpent or 0
local skillFree = false
local skillRepeat = 0
if c.kind == "Skill" and c.useAllEnergy ~= true and self.NextSkillCostZero == true then
skillFree = true
end
if c.kind == "Skill" and self.NextSkillRepeatCount ~= nil and self.NextSkillRepeatCount > 0 then
skillRepeat = self.NextSkillRepeatCount
end
self.ActiveKillReward = c.rewardOnKill or 0
self.ActiveKillMaxHpGain = c.maxHpOnKill or 0
self:ResolveCardEffects(cardId, 0, c, false, spent)
local function applyCardPlayHooks()
if self:HasPowerField("cardPlayedBlock") == true then
self:AddCardBlock(self:AddPowerFieldTotal("cardPlayedBlock"))
end
if c.cardPlayedDamage ~= nil and c.cardPlayedDamage > 0 then
self:DealDirectDamageToTarget(c.cardPlayedDamage)
end
if c.cardPlayedRandomDamage ~= nil and c.cardPlayedRandomDamage > 0 then
self:DealDirectDamageToRandomMonster(c.cardPlayedRandomDamage)
end
self:ApplyAttackCardPlayHooks(c)
self:ApplyHolyForceCardPlayHooks(c)
end
applyCardPlayHooks()
if skillRepeat > 0 then
local remaining = (self.NextSkillRepeatCount or 0) - skillRepeat
if remaining < 0 then remaining = 0 end
self.NextSkillRepeatCount = remaining
for i = 1, skillRepeat do
self:ResolveCardEffects(cardId, 0, c, false, spent)
applyCardPlayHooks()
end
end
if c.kind == "Attack" then
self.TurnAttackCardsPlayed = (self.TurnAttackCardsPlayed or 0) + 1
end
if skillFree == true and c.nextSkillCostZero ~= true then
self.NextSkillCostZero = false
end
if c.exhaust == true then
if self.ExhaustPile == nil then self.ExhaustPile = {} end
table.insert(self.ExhaustPile, cardId)
self:TriggerExhaustEffects(1)
elseif c.kind ~= "Power" then
table.insert(self.DiscardPile, cardId)
end
if self.ActiveKillReward ~= nil and self.ActiveKillReward <= 0 then
self.ActiveKillReward = 0
end
if self.ActiveKillMaxHpGain ~= nil and self.ActiveKillMaxHpGain <= 0 then
self.ActiveKillMaxHpGain = 0
end
return true`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'energySpent' },
], 0, 'boolean'),
method('TriggerDrawnCardAutoPlay', `if cardId == nil or cardId == "" or self.Hand == nil or self.PlayerPowers == nil then
return false
end
local c = self.Cards[cardId]
if c == nil or c.name == nil or c.name == "" then
return false
end
for i = 1, #self.PlayerPowers do
local powerCard = self.Cards[self.PlayerPowers[i]]
if powerCard ~= nil and powerCard.drawNameMatchAutoPlay ~= nil and powerCard.drawNameMatchAutoPlay ~= "" then
if string.find(c.name, powerCard.drawNameMatchAutoPlay, 1, true) ~= nil then
local foundSlot = 0
for hi = 1, #self.Hand do
if self.Hand[hi] == cardId then
foundSlot = hi
break
end
end
if foundSlot <= 0 then
return false
end
table.remove(self.Hand, foundSlot)
self:AutoPlayCardId(cardId, 0)
return true
end
end
end
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }], 0, 'boolean'),
method('PlayTopDrawPileCards', `if c == nil or self.DrawPile == nil then
return 0
end
local count = c.playTopDrawPileCount or 0
if c.playTopDrawPileCountPerEnergy ~= nil and c.playTopDrawPileCountPerEnergy > 0 then
count = count + ((energySpent or 0) * c.playTopDrawPileCountPerEnergy)
end
if count <= 0 then
return 0
end
local played = 0
for i = 1, count do
if #self.DrawPile <= 0 then
break
end
local topCardId = table.remove(self.DrawPile)
if topCardId ~= nil then
self:AutoPlayCardId(topCardId, 0)
played = played + 1
end
end
return played`, [
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'energySpent' },
], 0, 'number'),
method('AddRandomCardsFromEffect', `if c == nil or count == nil or count <= 0 then
return 0
end
local pool = {}
for id, rc in pairs(self.Cards) do
if rc ~= nil and rc.token ~= true and rc.curse ~= true and rc.unplayable ~= true then
local ok = true
if c.addRandomCardKind ~= nil and rc.kind ~= c.addRandomCardKind then ok = false end
if c.addRandomCardSameClass == true and rc.class ~= c.class then ok = false end
if ok == true then table.insert(pool, id) end
end
end
if #pool <= 0 then
return 0
end
local added = 0
for i = 1, count do
local cardId2 = pool[math.random(1, #pool)]
if cardId2 ~= nil then
self:AddCardsToHand(cardId2, 1)
if c.addedCardsCostZeroThisTurn == true then
self:MarkCardCostZeroThisTurn(cardId2)
end
added = added + 1
end
end
return added`, [
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'count' },
], 0, 'number'),
method('ExhaustHandNonAttack', `if c == nil or c.exhaustHandNonAttack ~= true or self.Hand == nil or #self.Hand <= 0 then
return 0
end
local exhausted = 0
for i = #self.Hand, 1, -1 do
local cardId2 = self.Hand[i]
local hc = self.Cards[cardId2]
if hc == nil or hc.kind ~= "Attack" then
table.remove(self.Hand, i)
if self.ExhaustPile == nil then self.ExhaustPile = {} end
table.insert(self.ExhaustPile, cardId2)
exhausted = exhausted + 1
end
end
if exhausted > 0 then
if c.blockPerExhaustedCard ~= nil and c.blockPerExhaustedCard > 0 then
self:AddCardBlock(exhausted * c.blockPerExhaustedCard)
end
self:TriggerExhaustEffects(exhausted)
end
return exhausted`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'number'),
method('ExhaustHandAll', `if c == nil or c.exhaustHandAll ~= true or self.Hand == nil or #self.Hand <= 0 then
return 0
end
local exhausted = 0
while #self.Hand > 0 do
local cardId2 = table.remove(self.Hand)
if self.ExhaustPile == nil then self.ExhaustPile = {} end
table.insert(self.ExhaustPile, cardId2)
exhausted = exhausted + 1
end
if exhausted > 0 then
self:TriggerExhaustEffects(exhausted)
end
return exhausted`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'number'),
method('CalcPlayerAttack', `local base2 = base
self.FightAttackCount = self.FightAttackCount + 1
if self.FightAttackCount == 1 and self:HasRelic("akabeko") then
@@ -485,10 +776,10 @@ if c.xWeakPerEnergy ~= nil and c.xWeakPerEnergy > 0 then
weakAmount = weakAmount + xEnergy * c.xWeakPerEnergy
end
if c.kind == "Attack" then
if c.damage ~= nil or c.xDamagePerEnergy ~= nil then
if c.damage ~= nil or c.xDamagePerEnergy ~= nil or c.damageFromCurrentBlock ~= nil then
self:PlayerAttackMotion()
local baseDmg = self:AttackBaseForCard(slot, c)
self.ActiveAttackDamageVsWeakMultiplier = c.attackDamageVsWeakMultiplier or 1
self.ActiveAttackDamageVsWeakMultiplier = math.max(c.attackDamageVsWeakMultiplier or 1, self:MaxPowerField("attackDamageVsWeakMultiplier"))
if c.xDamagePerEnergy ~= nil and c.xDamagePerEnergy > 0 then
baseDmg = xEnergy * c.xDamagePerEnergy
end
@@ -570,14 +861,14 @@ if c.kind == "Attack" then
self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + totalDamage
end
if c.block ~= nil then
self:AddCardBlock(c.block)
self:AddCardBlock(c.block + (self.HolyChargeCount or 0) * (c.blockPerHolyCharge or 0))
end
if free ~= true then
self:ApplyRelics("cardPlayed")
end
elseif c.kind == "Skill" then
if c.block ~= nil then
self:AddCardBlock(c.block)
self:AddCardBlock(c.block + (self.HolyChargeCount or 0) * (c.blockPerHolyCharge or 0))
end
elseif c.kind == "Power" then
if free ~= true then
@@ -597,11 +888,19 @@ if c.selfVuln ~= nil then
self.PlayerVuln = self.PlayerVuln + c.selfVuln
end
if c.heal ~= nil then
self.PlayerHp = math.min(self.PlayerHp + c.heal, self.PlayerMaxHp)
local healAmount = c.heal + (self.HolyChargeCount or 0) * (c.healPerHolyCharge or 0)
self.PlayerHp = math.min(self.PlayerHp + healAmount, self.PlayerMaxHp)
end
if c.gainEnergy ~= nil and c.gainEnergy ~= 0 then
self.Energy = self.Energy + c.gainEnergy
end
if c.kind ~= "Attack" and c.comboGain ~= nil and c.comboGain > 0 then
self:GainCombo(c.comboGain)
end
if c.removePlayerDebuffs == true then
self.PlayerWeak = 0
self.PlayerVuln = 0
end
if c.intangible ~= nil and c.intangible > 0 then
self.PlayerIntangible = (self.PlayerIntangible or 0) + c.intangible
end
@@ -719,6 +1018,23 @@ if c.drawSkillBlock ~= nil and c.drawSkillBlock > 0 then
end
if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then
self:AddCardsToHand("Shiv", c.addShiv)
end
local exhaustedNonAttack = self:ExhaustHandNonAttack(c)
local exhaustedAll = self:ExhaustHandAll(c)
local totalExhausted = exhaustedNonAttack + exhaustedAll
if c.drawPerExhausted ~= nil and c.drawPerExhausted > 0 and totalExhausted > 0 then
self:DrawCards(totalExhausted * c.drawPerExhausted, true)
end
if c.addRandomCardCount ~= nil and c.addRandomCardCount > 0 then
self:AddRandomCardsFromEffect(c, c.addRandomCardCount)
end
if c.addRandomCardPerExhausted ~= nil and c.addRandomCardPerExhausted > 0 then
if totalExhausted > 0 then
self:AddRandomCardsFromEffect(c, totalExhausted * c.addRandomCardPerExhausted)
end
end
if (c.playTopDrawPileCount ~= nil and c.playTopDrawPileCount > 0) or (c.playTopDrawPileCountPerEnergy ~= nil and c.playTopDrawPileCountPerEnergy > 0) then
self:PlayTopDrawPileCards(c, xEnergy)
end`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },

View File

@@ -77,9 +77,14 @@ for i = 1, 3 do
self:SetEntityEnabled(base, true)
self:SetText(base .. "/Name", o.name)
self:SetText(base .. "/Desc", o.desc)
local sc = self.Cards[o.starter]
local sc = nil
if o.starter ~= nil and o.starter ~= "" then
sc = self.Cards[o.starter]
end
if sc ~= nil then
self:SetText(base .. "/Starter", "대표 카드: " .. sc.name)
else
self:SetText(base .. "/Starter", "대표 카드: 계보 유지")
end
else
self:SetEntityEnabled(base, false)
@@ -111,6 +116,8 @@ if starter ~= "" then
if sc ~= nil then
self:Toast(tostring(tier) .. "차 전직: " .. self:JobLabel() .. "! 신규 카드 - " .. sc.name)
end
else
self:Toast(tostring(tier) .. "차 전직: " .. self:JobLabel() .. "!")
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false)

View File

@@ -80,6 +80,14 @@ if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "가시" .. tostring(self.PlayerThorns)
end
if self.ComboCount ~= nil and self.ComboCount > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "콤보 " .. tostring(self.ComboCount) .. "/" .. tostring(self:GetComboMax())
end
if self.HolyChargeCount ~= nil and self.HolyChargeCount > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "홀리 차지 " .. tostring(self.HolyChargeCount) .. "/" .. tostring(self:GetHolyChargeMax())
end
if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then
local names = {}
for i = 1, #self.PlayerPowers do

View File

@@ -1,4 +1,4 @@
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, ACT_DIFFICULTY_MULTIPLIERS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, JOB_META, CLASS_GROUPS, CLASS_LINEAGES, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaCharsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaClassGroupsTable, luaClassLineagesTable, luaJobMetaTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
@@ -95,11 +95,15 @@ self.PlayerVuln = 0
self.PlayerIntangible = 0
self.BonusRewardScreens = 0
self.ActiveKillReward = 0
self.ActiveKillMaxHpGain = 0
self.PlayerPowers = {}
self.FightAttackCount = 0
self.TurnAttackCardsPlayed = 0
self.TurnDiscardedCards = 0
self.TurnCardsPlayedThisTurn = 0
self.ComboCount = 0
self.HolyChargeCount = 0
self.DamagePowerStrengthUsed = false
self.DamageDealtThisTurn = 0
self.DmgPopSeq = 0
self.FirstHpLossDone = false
@@ -118,6 +122,7 @@ self.TurnAttackMultiplier = 1
self.NextTurnSelectPrompt = ""
self.NextTurnSelectCopies = 0
self.NextTurnAddCards = {}
self.ZeroCostCardIdsThisTurn = {}
self.CombatOver = false
self.DiscardPile = {}
self.ExhaustPile = {}
@@ -211,7 +216,8 @@ end
if #chosen == 0 then takeFrom(g, 1) end
if #chosen == 0 then takeFrom("combat", 1) end
table.sort(chosen, function(a, b) return a.x < b.x end)
local mult = 1 + (self.Floor - 1) * 0.45
local actMultipliers = { ${ACT_DIFFICULTY_MULTIPLIERS.join(', ')} }
local mult = actMultipliers[self.Floor] or actMultipliers[#actMultipliers]
if g == "elite" or g == "boss" then
mult = mult + self:AscEliteBonus()
end

View File

@@ -142,10 +142,14 @@ function writeCodeblocks() {
prop('number', 'PoisonApplicationsThisCombat', '0'),
prop('number', 'EnemyStrengthLossThisTurn', '0'),
prop('number', 'ActiveKillReward', '0'),
prop('number', 'ActiveKillMaxHpGain', '0'),
prop('number', 'BonusRewardScreens', '0'),
prop('number', 'FightAttackCount', '0'),
prop('number', 'TurnAttackCardsPlayed', '0'),
prop('number', 'TurnCardsPlayedThisTurn', '0'),
prop('number', 'ComboCount', '0'),
prop('number', 'HolyChargeCount', '0'),
prop('boolean', 'DamagePowerStrengthUsed', 'false'),
prop('number', 'DamageDealtThisTurn', '0'),
prop('number', 'TurnDiscardedCards', '0'),
prop('boolean', 'FirstHpLossDone', 'false'),
@@ -173,6 +177,7 @@ function writeCodeblocks() {
prop('boolean', 'NextSkillCostZero', 'false'),
prop('number', 'NextSkillRepeatCount', '0'),
prop('any', 'NextTurnAddCards'),
prop('any', 'ZeroCostCardIdsThisTurn'),
], [
...bootMethods,
...screensMethods,

View File

@@ -54,7 +54,8 @@ const REST_HEAL = 30;
const RELIC_PRICE = 60;
const ACT_COUNT = 5;
const ACT_MAPS = ['map01', 'map02', 'map03', 'map04', 'map05'];
const ACT_DIFFICULTY_MULTIPLIERS = [1, 1.075, 1.15, 1.3, 1.45];
const LOBBY_MAP = 'lobby';
const LOBBY_SPAWN = 'Vector3(-5, 0.03, 0)'; // 정찰: map01 지면 좌측
export { prop, method, codeblock, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN };
export { prop, method, codeblock, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, ACT_DIFFICULTY_MULTIPLIERS, LOBBY_MAP, LOBBY_SPAWN };

View File

@@ -19,9 +19,18 @@ for (const cls of Object.keys(CLASSES)) {
// 전직 옵션
const JOBS = {
warrior: [
{ id: 'fighter', name: '파이터', desc: '공격 특화\n콤보 어택 · 버서크\n라이징 어택', starter: 'ComboAttack', tier: 2, parent: 'warrior' },
{ id: 'page', name: '페이지', desc: '속성 차지 특화\n썬더/블리자드 차지\n파워 가드', starter: 'ThunderCharge', tier: 2, parent: 'warrior' },
{ id: 'spearman', name: '스피어맨', desc: '방어·관통 특화\n피어스 · 아이언 월\n하이퍼 바디', starter: 'Pierce', tier: 2, parent: 'warrior' },
{ id: 'fighter', name: '파이터', desc: '콤보와 다단 공격 특화\n공격으로 콤보를 쌓고\n추가타로 압박', starter: 'ComboAttack', tier: 2, parent: 'warrior' },
{ id: 'page', name: '페이지', desc: '홀리 포스와 방어 특화\n홀리 차지를 쌓아\n공격과 생존 강화', starter: 'HolyCharge', tier: 2, parent: 'warrior' },
{ id: 'spearman', name: '스피어맨', desc: '광역·장기전 계열\n대화재 · 소용돌이\n불의 심장', starter: 'Conflagration', tier: 2, parent: 'warrior' },
],
fighter: [
{ id: 'crusader', name: '크루세이더', desc: '파이터의 3차 전직\n콤보 상한과 연계 피해 강화\n파이터 카드 계승', starter: 'ComboSynergy', tier: 3, parent: 'fighter' },
],
page: [
{ id: 'knight', name: '나이트', desc: '페이지의 3차 전직\n홀리 차지를 공격·방어·회복으로 전환\n페이지 카드 계승', starter: 'DivineCharge', tier: 3, parent: 'page' },
],
spearman: [
{ id: 'berserker', name: '버서커', desc: '스피어맨의 3차 전직\n아이언클래드 장기전 풀 계승\n전사 카드 사용', starter: '', tier: 3, parent: 'spearman' },
],
magician: [
{ id: 'firepoison', name: '위자드(불·독)', desc: '화염·독 특화\n파이어 애로우\n포이즌 브레스 · 앰플', starter: 'FireArrow', tier: 2, parent: 'magician' },
@@ -29,24 +38,24 @@ const JOBS = {
{ id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal', tier: 2, parent: 'magician' },
],
rogue: [
{ id: 'assassin', name: 'Assassin', desc: '표창 중심 전직\n단일 화력과 독 압박\n빠른 마무리', starter: 'DeadlyPoison', tier: 2, parent: 'rogue' },
{ id: 'thief', name: 'Thief', desc: '단검 중심 전직\n드로우와 운영 강화\n빠른 연계', starter: 'Acrobatics', tier: 2, parent: 'rogue' },
{ id: 'assassin', name: 'Assassin', desc: '표창 중심 전직\n표창 생성과 연속 공격\n빠른 마무리', starter: 'JavelinAcceleration', tier: 2, parent: 'rogue' },
{ id: 'thief', name: 'Thief', desc: '단검 중심 전직\n드로우와 운영 강화\n빠른 연계', starter: 'DaggerAcceleration', tier: 2, parent: 'rogue' },
],
assassin: [
{ id: 'hermit', name: 'Hermit', desc: 'Assassin의 3차 전직\n표창과 독 운영 심화\n누적 압박 강화', starter: 'NoxiousFumes', tier: 3, parent: 'assassin' },
{ id: 'hermit', name: 'Hermit', desc: 'Assassin의 3차 전직\n표창 생성과 강화 심화\n연속 공격 완성', starter: 'SpiritJavelin', tier: 3, parent: 'assassin' },
],
thief: [
{ id: 'thiefmaster', name: 'Thief Master', desc: 'Thief의 3차 전직\n단검 운영 심화\n드로우와 템포 강화', starter: 'ToolsOfTheTrade', tier: 3, parent: 'thief' },
{ id: 'thiefmaster', name: 'Thief Master', desc: 'Thief의 3차 전직\n단검·교활·중독 심화\n연계 운영 완성', starter: 'Venom', tier: 3, parent: 'thief' },
],
};
for (const [cls, jobs] of Object.entries(JOBS)) {
for (const j of jobs) {
if (!CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
if (j.starter && !CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
}
}
const CLASS_GROUPS = {
warrior: ['warrior', 'fighter', 'page', 'spearman'],
warrior: ['warrior', 'fighter', 'crusader', 'page', 'knight', 'spearman', 'berserker'],
magician: ['magician', 'firepoison', 'icelightning', 'cleric'],
rogue: ['rogue', 'assassin', 'hermit', 'thief', 'thiefmaster'],
};
@@ -54,8 +63,11 @@ const CLASS_GROUPS = {
const CLASS_LINEAGES = {
warrior: ['warrior'],
fighter: ['warrior', 'fighter'],
crusader: ['warrior', 'fighter', 'crusader'],
page: ['warrior', 'page'],
knight: ['warrior', 'page', 'knight'],
spearman: ['warrior', 'spearman'],
berserker: ['warrior', 'spearman', 'berserker'],
magician: ['magician'],
firepoison: ['magician', 'firepoison'],
icelightning: ['magician', 'icelightning'],
@@ -212,6 +224,12 @@ function luaCardsTable(cards) {
const lines = Object.entries(cards).map(([id, c]) => {
const fields = [`name = ${luaStr(c.name)}`, `cost = ${c.cost}`, `desc = ${luaStr(c.desc)}`, `kind = ${luaStr(c.kind)}`];
if (c.damage != null) fields.push(`damage = ${c.damage}`);
if (c.damageFromCurrentBlock != null) fields.push(`damageFromCurrentBlock = ${c.damageFromCurrentBlock}`);
if (c.damagePerCombo != null) fields.push(`damagePerCombo = ${c.damagePerCombo}`);
if (c.damagePerHolyCharge != null) fields.push(`damagePerHolyCharge = ${c.damagePerHolyCharge}`);
if (c.attackDamagePerCombo != null) fields.push(`attackDamagePerCombo = ${c.attackDamagePerCombo}`);
if (c.damageNameMatch != null) fields.push(`damageNameMatch = ${luaStr(c.damageNameMatch)}`);
if (c.damagePerOwnedNameMatch != null) fields.push(`damagePerOwnedNameMatch = ${c.damagePerOwnedNameMatch}`);
if (c.damagePerOtherHandCard != null) fields.push(`damagePerOtherHandCard = ${c.damagePerOtherHandCard}`);
if (c.damagePerAttackPlayedThisTurn != null) fields.push(`damagePerAttackPlayedThisTurn = ${c.damagePerAttackPlayedThisTurn}`);
if (c.damagePerDiscardedThisTurn != null) fields.push(`damagePerDiscardedThisTurn = ${c.damagePerDiscardedThisTurn}`);
@@ -220,8 +238,10 @@ function luaCardsTable(cards) {
if (c.damagePerTurn != null) fields.push(`damagePerTurn = ${c.damagePerTurn}`);
if (c.cardPlayedDamage != null) fields.push(`cardPlayedDamage = ${c.cardPlayedDamage}`);
if (c.cardPlayedRandomDamage != null) fields.push(`cardPlayedRandomDamage = ${c.cardPlayedRandomDamage}`);
if (c.attackPlayedDamage != null) fields.push(`attackPlayedDamage = ${c.attackPlayedDamage}`);
if (c.firstCardDamageBonus != null) fields.push(`firstCardDamageBonus = ${c.firstCardDamageBonus}`);
if (c.rewardOnKill != null) fields.push(`rewardOnKill = ${c.rewardOnKill}`);
if (c.maxHpOnKill != null) fields.push(`maxHpOnKill = ${c.maxHpOnKill}`);
if (c.intangible != null) fields.push(`intangible = ${c.intangible}`);
if (c.endTurnDexLoss != null) fields.push(`endTurnDexLoss = ${c.endTurnDexLoss}`);
if (c.poisonPerTurn != null) fields.push(`poisonPerTurn = ${c.poisonPerTurn}`);
@@ -235,6 +255,16 @@ function luaCardsTable(cards) {
if (c.dex != null) fields.push(`dex = ${c.dex}`);
if (c.thorns != null) fields.push(`thorns = ${c.thorns}`);
if (c.cardPlayedBlock != null) fields.push(`cardPlayedBlock = ${c.cardPlayedBlock}`);
if (c.comboOnAttack != null) fields.push(`comboOnAttack = ${c.comboOnAttack}`);
if (c.comboMax != null) fields.push(`comboMax = ${c.comboMax}`);
if (c.attackWeak != null) fields.push(`attackWeak = ${c.attackWeak}`);
if (c.holyChargeOnHolyForce != null) fields.push(`holyChargeOnHolyForce = ${c.holyChargeOnHolyForce}`);
if (c.holyChargeMax != null) fields.push(`holyChargeMax = ${c.holyChargeMax}`);
if (c.damageTakenReduction != null) fields.push(`damageTakenReduction = ${c.damageTakenReduction}`);
if (c.blockOnDamaged != null) fields.push(`blockOnDamaged = ${c.blockOnDamaged}`);
if (c.strengthOnDamagedOnce != null) fields.push(`strengthOnDamagedOnce = ${c.strengthOnDamagedOnce}`);
if (c.drawOnExhaust != null) fields.push(`drawOnExhaust = ${c.drawOnExhaust}`);
if (c.drawNameMatchAutoPlay != null) fields.push(`drawNameMatchAutoPlay = ${luaStr(c.drawNameMatchAutoPlay)}`);
if (c.weak != null) fields.push(`weak = ${c.weak}`);
if (c.vuln != null) fields.push(`vuln = ${c.vuln}`);
if (c.powerEffect != null) fields.push(`powerEffect = ${luaStr(c.powerEffect)}`);
@@ -250,8 +280,26 @@ function luaCardsTable(cards) {
if (c.drawSkillBlock != null) fields.push(`drawSkillBlock = ${c.drawSkillBlock}`);
if (c.drawDamage != null) fields.push(`drawDamage = ${c.drawDamage}`);
if (c.drawPoison != null) fields.push(`drawPoison = ${c.drawPoison}`);
if (c.exhaustHandNonAttack === true) fields.push('exhaustHandNonAttack = true');
if (c.exhaustHandAll === true) fields.push('exhaustHandAll = true');
if (c.drawPerExhausted != null) fields.push(`drawPerExhausted = ${c.drawPerExhausted}`);
if (c.blockPerExhaustedCard != null) fields.push(`blockPerExhaustedCard = ${c.blockPerExhaustedCard}`);
if (c.addRandomCardCount != null) fields.push(`addRandomCardCount = ${c.addRandomCardCount}`);
if (c.addRandomCardPerExhausted != null) fields.push(`addRandomCardPerExhausted = ${c.addRandomCardPerExhausted}`);
if (c.addRandomCardKind != null) fields.push(`addRandomCardKind = ${luaStr(c.addRandomCardKind)}`);
if (c.addRandomCardSameClass === true) fields.push('addRandomCardSameClass = true');
if (c.addedCardsCostZeroThisTurn === true) fields.push('addedCardsCostZeroThisTurn = true');
if (c.playTopDrawPileCount != null) fields.push(`playTopDrawPileCount = ${c.playTopDrawPileCount}`);
if (c.playTopDrawPileCountPerEnergy != null) fields.push(`playTopDrawPileCountPerEnergy = ${c.playTopDrawPileCountPerEnergy}`);
if (c.heal != null) fields.push(`heal = ${c.heal}`);
if (c.healPerHolyCharge != null) fields.push(`healPerHolyCharge = ${c.healPerHolyCharge}`);
if (c.gainEnergy != null) fields.push(`gainEnergy = ${c.gainEnergy}`);
if (c.comboGain != null) fields.push(`comboGain = ${c.comboGain}`);
if (c.removePlayerDebuffs === true) fields.push('removePlayerDebuffs = true');
if (c.holyForce === true) fields.push('holyForce = true');
if (c.holyChargeGain != null) fields.push(`holyChargeGain = ${c.holyChargeGain}`);
if (c.blockPerHolyCharge != null) fields.push(`blockPerHolyCharge = ${c.blockPerHolyCharge}`);
if (c.holyChargeSpendAll === true) fields.push('holyChargeSpendAll = true');
if (c.poison != null) fields.push(`poison = ${c.poison}`);
if (c.discard != null) fields.push(`discard = ${c.discard}`);
if (c.discardAll === true) fields.push('discardAll = true');

View File

@@ -1,108 +1,55 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { buildMonsterInstance } from '../monster/lib/monster-model.mjs';
// map02~11에 노드 타입별 몬스터 그룹(combat3/elite2/boss1)을 맵별 테마로 자동 구성.
// 기존 몬스터 엔티티 전부 제거하고 첫 몬스터를 템플릿으로 6마리 재생성(결정론).
const MAP_NUMBERS = [1, 2, 3, 4, 5];
const COMBAT_POOL = ['orange_mushroom', 'green_mushroom', 'pig', 'blue_mushroom', 'red_snail', 'stump'];
const ELITE_POOL = ['mushmom', 'modified_snail'];
const BOSS_POOL = ['king_slime', 'slime_boss'];
// map01: StS2식 일반 5종 + 엘리트 1 + 보스 1(보스 노드용, 화면 우측 포메이션).
// 그 외 맵: 일반 3 + 엘리트 2 + 보스 1. 전투 시 BuildMonsters가 노드 타입별로 1~3마리 랜덤 추첨.
const LAYOUT_MAP01 = [
{ group: 'combat', x: 2.6 }, { group: 'combat', x: 3.6 }, { group: 'combat', x: 4.6 },
{ group: 'combat', x: 5.6 }, { group: 'combat', x: 6.6 },
{ group: 'elite', x: 4.6 },
{ group: 'boss', x: 4.6 },
];
const LAYOUT_DEFAULT = [
{ group: 'combat', x: 2.3 }, { group: 'combat', x: 3.8 }, { group: 'combat', x: 5.2 },
{ group: 'elite', x: 3.0 }, { group: 'elite', x: 5.0 },
{ group: 'boss', x: 4.0 },
];
const layoutFor = (nn) => (nn === 1 ? LAYOUT_MAP01 : LAYOUT_DEFAULT);
const MONSTER_VARIANTS = [
{ sprite: '96e955c1bf27415e84f96deea200a8f1', stand: '96e955c1bf27415e84f96deea200a8f1', hit: 'aec9504d5dc24aceb5646b79d30abad4', die: '65a2bfb039614f2e9e4ccc354340153d' },
{ sprite: 'f86992ba9c41487c8480fcb893fcbda6', stand: 'f86992ba9c41487c8480fcb893fcbda6', hit: 'd305b942b1704c8084548108ff3b7a6b', die: '5a563e5fd98c4132b61057dc6bb8aaf2' },
{ sprite: 'a2204a21d88942b281d2cac6053ffbaa', stand: 'a2204a21d88942b281d2cac6053ffbaa', hit: 'afc08936b8a64b26bc3dd8c03ead1f26', die: 'fc1c6d9ba9bc413ab53b6dbfae3ac45b' },
{ sprite: 'd8f014043ce8418f96700c2b6c9ebf6c', stand: 'd8f014043ce8418f96700c2b6c9ebf6c', hit: 'c3cf643b618346c7bfa6574187b396f9', die: 'a88d9b3d60f941e4890dc89a6ccaa8ee' },
{ sprite: '17b55730c26f4fd6b8fcfa288da388de', stand: '17b55730c26f4fd6b8fcfa288da388de', hit: 'eac48e84a9fc4580a4018de5cf52ddb3', die: '51c2f4b59a2c413db26035aa57002fc8' },
{ sprite: '48c10437ae8344a9b2a1d3f36185728f', stand: '48c10437ae8344a9b2a1d3f36185728f', hit: '9044063647854f5e9128efcf80e909be', die: 'f414577d18c94cc387c275df4abdbc3b' },
{ sprite: '4ca39dbfa1c6492283ba8bd352d12b0a', stand: '4ca39dbfa1c6492283ba8bd352d12b0a', hit: '7ac78511036e4ebe988b97c35fc275d1', die: '740f3f2b2e7a4b71bec5eac84e8539f9' },
{ sprite: 'ed3908e24d694bb786023fc1ed073489', stand: 'ed3908e24d694bb786023fc1ed073489', hit: '4763c9bebc9245998c9c499b6316aa9f', die: 'b168793b92a844a3a3a6f4ce647a14d2' },
{ sprite: '3109357701ae41a4bcc7543f52f1f4c3', stand: '3109357701ae41a4bcc7543f52f1f4c3', hit: 'ce0269079e884545b5bb6ea075e2a67f', die: 'a5e65650e00e47878cac1be7a5b999a0' },
];
// map01~05에 data/encounters.json 로스터대로 종별 모델 인스턴스를 배치(결정론).
// 기존 몬스터 엔티티 전부 제거 후 로스터 전체를 그룹별 x 균등 분포로 재생성.
// 준비도 가드: 로스터에 appearance 미보유 적이 있는 맵은 재생성을 건너뛴다(기존 맵 보존).
const enemies = JSON.parse(readFileSync('data/enemies.json', 'utf8')).enemies;
const encounters = JSON.parse(readFileSync('data/encounters.json', 'utf8'));
const X_RANGE = { combat: [2.3, 6.6], elite: [3.0, 5.6], boss: [4.6, 4.6] };
function rng(seed) { let s = seed >>> 0; return () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 4294967296; }; }
const isMonster = (e) => typeof e.componentNames === 'string' && e.componentNames.includes('script.Monster');
function encGuid(nn, idx) {
const n = (nn * 1000 + 500 + idx) >>> 0;
const h8 = n.toString(16).padStart(8, '0');
const h12 = n.toString(16).padStart(12, '0');
return `${h8}-0000-4000-8000-${h12}`;
return `${n.toString(16).padStart(8, '0')}-0000-4000-8000-${n.toString(16).padStart(12, '0')}`;
}
const isMonster = (e) => typeof e.componentNames === 'string' && e.componentNames.includes('script.Monster');
const compOf = (e, t) => e.jsonString['@components'].find((c) => c['@type'] === t);
function pick(rand, pool) { return pool[Math.floor(rand() * pool.length)]; }
function pickN(rand, pool, n) {
const a = pool.slice();
const out = [];
for (let i = 0; i < n; i++) {
if (a.length === 0) a.push(...pool);
out.push(a.splice(Math.floor(rand() * a.length), 1)[0]);
}
return out;
function slotX(group, i, count) {
const [lo, hi] = X_RANGE[group];
return count <= 1 ? (lo + hi) / 2 : lo + (i * (hi - lo)) / (count - 1);
}
function patchMap(nn) {
const tag = String(nn).padStart(2, '0');
const file = `map/map${tag}.map`;
const map = JSON.parse(readFileSync(file, 'utf8'));
const ents = map.ContentProto.Entities;
const monsters = ents.filter(isMonster);
if (monsters.length === 0) throw new Error(`[gen-map-encounters] ${file} 몬스터 템플릿 없음`);
const template = monsters[0];
map.ContentProto.Entities = ents.filter((e) => !isMonster(e));
const rand = rng(nn * 7919 + 17);
const layout = layoutFor(nn);
const nCombat = layout.filter((s) => s.group === 'combat').length;
const nElite = layout.filter((s) => s.group === 'elite').length;
const combatIds = pickN(rand, COMBAT_POOL, nCombat);
const eliteIds = pickN(rand, ELITE_POOL, nElite);
const bossId = pick(rand, BOSS_POOL);
const variants = pickN(rand, MONSTER_VARIANTS, layout.length);
let ci = 0, ei = 0;
layout.forEach((slot, idx) => {
const m = JSON.parse(JSON.stringify(template));
const enemyId = slot.group === 'combat' ? combatIds[ci++] : slot.group === 'elite' ? eliteIds[ei++] : bossId;
const name = `${slot.group}_${idx + 1}`;
m.id = encGuid(nn, idx);
m.path = `/maps/map${tag}/${name}`;
m.jsonString.path = m.path;
m.jsonString.name = name;
const o = m.jsonString.origin;
if (o) { if (o.root_entity_id) o.root_entity_id = m.id; if (o.sub_entity_id) o.sub_entity_id = m.id; }
const tr = compOf(m, 'MOD.Core.TransformComponent');
if (tr && tr.Position) tr.Position.x = slot.x;
const v = variants[idx];
const sp = compOf(m, 'MOD.Core.SpriteRendererComponent');
if (sp) sp.SpriteRUID = v.stand;
const sa = compOf(m, 'MOD.Core.StateAnimationComponent');
if (sa) sa.ActionSheet = { stand: v.stand, hit: v.hit, die: v.die };
let cm = compOf(m, 'script.CombatMonster');
if (!cm) {
cm = { '@type': 'script.CombatMonster', Enable: true };
m.jsonString['@components'].push(cm);
const names = (m.componentNames || '').split(',').filter((s) => s && s !== 'script.CombatMonster');
names.push('script.CombatMonster');
m.componentNames = names.join(',');
const roster = encounters[`map${tag}`];
if (!roster) throw new Error(`[gen-map-encounters] encounters.json에 map${tag} 없음`);
const rosterIds = ['combat', 'elite', 'boss'].flatMap((g) => roster[g] || []);
for (const id of rosterIds) {
if (!enemies[id]) throw new Error(`[gen-map-encounters] map${tag} 로스터에 없는 적: ${id}`);
}
cm.EnemyId = enemyId;
cm.Group = slot.group;
map.ContentProto.Entities.push(m);
// 준비도 가드: appearance 미보유 적이 하나라도 있으면 이 맵은 보존(스킵)
const missing = rosterIds.filter((id) => !enemies[id].appearance);
if (missing.length) return `map${tag}(SKIP: appearance 없음 ${[...new Set(missing)].join('/')})`;
const map = JSON.parse(readFileSync(file, 'utf8'));
map.ContentProto.Entities = map.ContentProto.Entities.filter((e) => !isMonster(e));
const nameCount = {};
let idx = 0;
for (const group of ['combat', 'elite', 'boss']) {
const ids = roster[group] || [];
ids.forEach((enemyId, i) => {
nameCount[enemyId] = (nameCount[enemyId] || 0) + 1;
const name = nameCount[enemyId] > 1 ? `${enemyId}_${nameCount[enemyId]}` : enemyId;
map.ContentProto.Entities.push(buildMonsterInstance({
enemyId, enemy: enemies[enemyId], name, guid: encGuid(nn, idx), mapTag: tag, x: slotX(group, i, ids.length), group,
}));
idx += 1;
});
}
writeFileSync(file, JSON.stringify(map, null, 2), 'utf8');
return `map${tag}(${combatIds.join('/')}|${eliteIds.join('/')}|${bossId})`;
const counts = ['combat', 'elite', 'boss'].map((g) => `${g}${(roster[g] || []).length}`).join('/');
return `map${tag}(${counts})`;
}
const made = MAP_NUMBERS.map(patchMap);
const made = [1, 2, 3, 4, 5].map(patchMap);
console.log('Encounters:', made.join(', '));

View File

@@ -1,10 +1,8 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { writeFileSync } from 'node:fs';
// 맵 몬스터에 적 타입(EnemyId)을 부여하고, BeginPlay 시 /common 컨트롤러에 자기등록하는 마커.
// 카드 전투 시 컨트롤러가 등록 목록으로 인카운터를 구성한다.
const MAP_NUMBERS = Array.from({ length: 5 }, (_, i) => i + 1); // map01~05
const NAME_TO_ENEMY = { '주황버섯': 'orange_mushroom', '파란버섯': 'blue_mushroom' };
const DEFAULT_ENEMY = 'orange_mushroom';
// 카드 전투용 자기등록 마커 codeblock(CombatMonster) 생성.
// BeginPlay 시 /common 컨트롤러에 자기등록해 인카운터를 구성한다.
// 맵 부착 값(EnemyId/Group)은 gen-map-encounters.mjs가 인스턴스에 직접 기록한다.
function prop(Type, Name, DefaultValue = 'nil') {
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
@@ -49,39 +47,5 @@ eventId = _TimerService:SetTimerRepeat(reg, 0.1)`),
writeFileSync('RootDesk/MyDesk/CombatMonster.codeblock', JSON.stringify(cb, null, 2) + '\n', 'utf8');
}
const isMonster = (e) => typeof e.componentNames === 'string' && e.componentNames.includes('script.Monster');
function patchMap(nn) {
const tag = String(nn).padStart(2, '0');
const file = `map/map${tag}.map`;
const map = JSON.parse(readFileSync(file, 'utf8'));
let added = 0, kept = 0;
for (const e of map.ContentProto.Entities.filter(isMonster)) {
const comps = e.jsonString && e.jsonString['@components'];
if (!Array.isArray(comps)) {
console.warn(`[gen-combat-monster] entity "${(e.jsonString && e.jsonString.name) || e.path}" has no @components — skipped`);
continue;
}
const name = (e.jsonString && e.jsonString.name) || '';
const existing = comps.find((c) => c['@type'] === 'script.CombatMonster');
if (existing) {
// 사용자가 메이커에서 설정한 값 보존 — 누락된 키만 기본값 채움
if (existing.Enable === undefined) existing.Enable = true;
if (existing.EnemyId == null) existing.EnemyId = NAME_TO_ENEMY[name] || DEFAULT_ENEMY;
if (existing.Group == null) existing.Group = 'combat';
kept++;
} else {
comps.push({ '@type': 'script.CombatMonster', Enable: true, EnemyId: NAME_TO_ENEMY[name] || DEFAULT_ENEMY, Group: 'combat' });
added++;
}
const names = (e.componentNames || '').split(',').filter((s) => s && s !== 'script.CombatMonster');
names.push('script.CombatMonster');
e.componentNames = names.join(',');
}
writeFileSync(file, JSON.stringify(map, null, 2), 'utf8');
return `map${tag}(+${added}/keep${kept})`;
}
writeCodeblock();
const patched = MAP_NUMBERS.map(patchMap);
console.log('CombatMonster codeblock written; patched maps:', patched.join(', '));
console.log('CombatMonster codeblock written.');

View File

@@ -0,0 +1,29 @@
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { buildMonsterModel, modelEntryId } from './lib/monster-model.mjs';
// 적 18종 각각의 전용 모델(.model) emit. 단일 소스: data/enemies.json(appearance) + ChaseMonster.model(골격).
const OUT_DIR = 'RootDesk/MyDesk/Models/Monsters';
const enemies = JSON.parse(readFileSync('data/enemies.json', 'utf8')).enemies;
const skeleton = JSON.parse(readFileSync('Global/ChaseMonster.model', 'utf8'));
// EntryKey 충돌 가드 (LEA-3015 예방): 기존 .model들의 EntryKey 수집 (경로별)
const existing = []; // { key, path }
for (const dir of ['Global', OUT_DIR]) {
for (const f of readdirSync(dir).filter((n) => n.endsWith('.model'))) {
const path = `${dir}/${f}`;
existing.push({ key: JSON.parse(readFileSync(path, 'utf8')).EntryKey, path });
}
}
const written = [];
const skipped = [];
for (const [enemyId, enemy] of Object.entries(enemies)) {
if (!enemy.appearance) { skipped.push(enemyId); continue; }
const file = buildMonsterModel(enemyId, enemy, skeleton);
const outPath = `${OUT_DIR}/${enemyId}.model`;
const clash = existing.find((e) => e.key === file.EntryKey && e.path !== outPath);
if (clash) throw new Error(`[gen-monster-models] EntryKey 충돌: ${file.EntryKey} (기존 ${clash.path})`);
writeFileSync(outPath, JSON.stringify(file, null, 2) + '\n', 'utf8');
written.push(enemyId);
}
console.log(`[gen-monster-models] ${written.length}종 emit${skipped.length ? ` / appearance 없음 스킵: ${skipped.join(', ')}` : ''}`);

View File

@@ -0,0 +1,78 @@
// 몬스터 종별 모델(.model)과 맵 인스턴스 엔티티의 공용 빌더.
// 단일 소스: data/enemies.json의 appearance. fs 접근 없음(호출자가 skeleton 주입) — 테스트 용이.
const STR_TYPE = 'System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089';
const SINGLE_TYPE = 'System.Single, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089';
const VEC2_TYPE = 'MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null';
const DICT_TYPE = 'MOD.Core.MODSyncDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null';
const native = (type) => ({ $type: 'MODNativeType', type });
const vec2 = (v) => ({ $type: 'MOD.Core.MODVector2, MOD.Core', x: v.x, y: v.y });
const DAMAGE_SKIN_ID = '02c22d93421b4038b3c413b3e40b57ec';
export function modelEntryId(enemyId) {
return `monster-${enemyId}`;
}
function requireAppearance(enemyId, enemy) {
if (!enemy?.appearance?.sheet?.stand) throw new Error(`[monster-model] ${enemyId}: appearance.sheet.stand 없음 — data/enemies.json 확인`);
return enemy.appearance;
}
// .model 파일 전체 객체 생성 — ChaseMonster.model(skeleton)을 골격으로 복제·확장.
export function buildMonsterModel(enemyId, enemy, skeletonJson) {
const app = requireAppearance(enemyId, enemy);
const file = JSON.parse(JSON.stringify(skeletonJson)); // 순수성: 입력 비변형
const json = file.ContentProto.Json;
file.EntryKey = `model://${modelEntryId(enemyId)}`;
json.Id = modelEntryId(enemyId);
json.Name = enemyId;
json.Components = json.Components
.filter((c) => !c.includes('AIWander') && !c.includes('AIChase'))
.concat(['MOD.Core.DamageSkinSettingComponent', 'script.CombatMonster']);
const setValue = (TargetType, Name, typeStr, Value) => {
json.Values = json.Values.filter((v) => !(v.TargetType === TargetType && v.Name === Name));
json.Values.push({ TargetType, Name, ValueType: native(typeStr), Value });
};
setValue('MOD.Core.SpriteRendererComponent', 'SpriteRUID', STR_TYPE, app.sheet.stand);
setValue('MOD.Core.SpriteRendererComponent', 'SortingLayer', STR_TYPE, 'MapLayer0');
setValue('MOD.Core.StateAnimationComponent', 'ActionSheet', DICT_TYPE, { ...app.sheet }); // 메이커 미해석 시 이 줄만 제거(런타임은 인스턴스 값 사용)
setValue('MOD.Core.HitComponent', 'BoxSize', VEC2_TYPE, vec2(app.box));
setValue('MOD.Core.HitComponent', 'ColliderOffset', VEC2_TYPE, vec2(app.off));
setValue('MOD.Core.MovementComponent', 'InputSpeed', SINGLE_TYPE, 0);
setValue('script.CombatMonster', 'EnemyId', STR_TYPE, enemyId); // 편의 베이크 — 실패해도 무해(인스턴스가 정본)
return file;
}
// 맵 인스턴스 엔티티 — 현행 맵 몬스터 인스턴스 골격(map01 실측)과 동일 형태.
export function buildMonsterInstance({ enemyId, enemy, name, guid, mapTag, x, group }) {
const app = requireAppearance(enemyId, enemy);
const components = [
{ '@type': 'MOD.Core.TransformComponent', Position: { x, y: 0.03499998, z: 999.999 }, QuaternionRotation: { x: 0, y: 0, z: 0, w: 1 }, Scale: { x: 1, y: 1, z: 1 }, Enable: true },
{ '@type': 'MOD.Core.StateAnimationComponent', ActionSheet: { ...app.sheet }, Enable: true },
{ '@type': 'MOD.Core.SpriteRendererComponent', ActionSheet: {}, EndFrameIndex: 0, RenderSetting: 1, SortingLayer: 'MapLayer0', SpriteRUID: app.sheet.stand, StartFrameIndex: 0, Enable: true },
{ '@type': 'MOD.Core.RigidbodyComponent', MoveVelocity: { x: 0, y: 0 }, RealMoveVelocity: { x: 0, y: 0 }, Enable: true },
{ '@type': 'MOD.Core.MovementComponent', InputSpeed: 0, JumpForce: 6, Enable: false },
{ '@type': 'MOD.Core.StateComponent', IsLegacy: false, Enable: true },
{ '@type': 'MOD.Core.HitComponent', BoxSize: { x: app.box.x, y: app.box.y }, ColliderOffset: { x: app.off.x, y: app.off.y }, IsLegacy: false, Enable: true },
{ '@type': 'MOD.Core.DamageSkinSpawnerComponent', Enable: true },
{ '@type': 'script.Monster', Enable: true, IsDead: false },
{ '@type': 'script.MonsterAttack', Enable: true, SpriteSize: { x: 0, y: 0 }, PositionOffset: { x: 0, y: 0 } },
{ '@type': 'MOD.Core.KinematicbodyComponent', MoveVelocity: { x: 0, y: 0 }, Enable: true },
{ '@type': 'MOD.Core.SideviewbodyComponent', MoveVelocity: { x: 0, y: 0 }, Enable: true },
{ '@type': 'MOD.Core.DamageSkinSettingComponent', DamageSkinId: { DataId: DAMAGE_SKIN_ID }, Enable: true },
{ '@type': 'script.CombatMonster', Enable: true, EnemyId: enemyId, Group: group },
];
const path = `/maps/map${mapTag}/${name}`;
return {
id: guid,
path,
componentNames: components.map((c) => c['@type']).join(','),
jsonString: {
name, path, nameEditable: true, enable: true, visible: true, localize: false,
displayOrder: 4, pathConstraints: '///', revision: 2,
origin: { type: 'Model', entry_id: modelEntryId(enemyId), sub_entity_id: null, root_entity_id: guid, replaced_model_id: null },
modelId: modelEntryId(enemyId),
'@components': components,
'@version': 1,
},
};
}

View File

@@ -0,0 +1,60 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { buildMonsterModel, buildMonsterInstance, modelEntryId } from './lib/monster-model.mjs';
const skeleton = JSON.parse(readFileSync('Global/ChaseMonster.model', 'utf8'));
const enemy = {
name: '슬라임', maxHp: 45, intents: [],
appearance: { sheet: { stand: 'AAAA', hit: 'BBBB', die: 'CCCC' }, box: { x: 0.63, y: 0.58 }, off: { x: 0.045, y: 0.29 } },
};
test('modelEntryId: monster- 접두', () => {
assert.equal(modelEntryId('slime'), 'monster-slime');
});
test('buildMonsterModel: EntryKey/Id/Name 파생', () => {
const m = buildMonsterModel('slime', enemy, skeleton);
assert.equal(m.EntryKey, 'model://monster-slime');
assert.equal(m.ContentProto.Json.Id, 'monster-slime');
assert.equal(m.ContentProto.Json.Name, 'slime');
});
test('buildMonsterModel: 외형·EnemyId 베이크 + AI-free + 컴포넌트 확장', () => {
const j = buildMonsterModel('slime', enemy, skeleton).ContentProto.Json;
assert.ok(j.Components.includes('script.CombatMonster'));
assert.ok(j.Components.includes('MOD.Core.DamageSkinSettingComponent'));
assert.ok(!j.Components.some((c) => c.includes('AIWander') || c.includes('AIChase')));
const val = (t, n) => j.Values.find((v) => v.TargetType === t && v.Name === n)?.Value;
assert.equal(val('MOD.Core.SpriteRendererComponent', 'SpriteRUID'), 'AAAA');
assert.deepEqual(val('MOD.Core.StateAnimationComponent', 'ActionSheet'), enemy.appearance.sheet);
assert.equal(val('script.CombatMonster', 'EnemyId'), 'slime');
assert.equal(val('MOD.Core.MovementComponent', 'InputSpeed'), 0);
assert.equal(val('MOD.Core.HitComponent', 'BoxSize').x, 0.63);
});
test('buildMonsterModel: 원본 skeleton 비변형(순수 함수)', () => {
const before = JSON.stringify(skeleton);
buildMonsterModel('slime', enemy, skeleton);
assert.equal(JSON.stringify(skeleton), before);
});
test('buildMonsterInstance: 모델 연결·컴포넌트 값', () => {
const e = buildMonsterInstance({ enemyId: 'slime', enemy, name: 'slime', guid: '00000bb9-0000-4000-8000-000000000bb9', mapTag: '03', x: 3.4, group: 'elite' });
assert.equal(e.jsonString.modelId, 'monster-slime');
assert.equal(e.jsonString.origin.entry_id, 'monster-slime');
assert.equal(e.jsonString.origin.root_entity_id, e.id);
assert.equal(e.path, '/maps/map03/slime');
const comp = (t) => e.jsonString['@components'].find((c) => c['@type'] === t);
assert.equal(comp('script.CombatMonster').EnemyId, 'slime');
assert.equal(comp('script.CombatMonster').Group, 'elite');
assert.equal(comp('MOD.Core.TransformComponent').Position.x, 3.4);
assert.equal(comp('MOD.Core.SpriteRendererComponent').SpriteRUID, 'AAAA');
assert.deepEqual(comp('MOD.Core.StateAnimationComponent').ActionSheet, enemy.appearance.sheet);
assert.equal(comp('MOD.Core.MovementComponent').Enable, false);
assert.equal(e.componentNames, e.jsonString['@components'].map((c) => c['@type']).join(','));
});
test('buildMonsterInstance: appearance 없는 적은 에러(fail-fast)', () => {
assert.throws(() => buildMonsterInstance({ enemyId: 'x', enemy: { name: 'x' }, name: 'x', guid: 'g', mapTag: '01', x: 1, group: 'combat' }), /appearance/);
});

View File

@@ -17,6 +17,10 @@ const POWER_FIELDS = [
'shivDamageBonus', 'firstShivDamageBonus', 'shivRetain', 'shivAoe',
'attackPoison', 'drawDamage', 'drawPoison', 'attackDamageVsWeakMultiplier',
'cardPlayedBlock', 'cardPlayedDamage', 'cardPlayedRandomDamage',
'comboOnAttack', 'comboMax', 'attackDamagePerCombo', 'attackPlayedDamage', 'attackWeak',
'holyChargeOnHolyForce', 'holyChargeMax', 'damageTakenReduction',
'blockOnDamaged', 'strengthOnDamagedOnce',
'drawOnExhaust', 'drawNameMatchAutoPlay',
'extraPoisonTicks', 'poisonApplicationBurstEvery', 'poisonApplicationBurstDamage',
'skillSlyOnPlay', 'endTurnDexLoss',
];
@@ -28,7 +32,7 @@ for (const [id, c] of Object.entries(cards)) {
issues.push(`${id}(${c.name}): 미지원 kind="${c.kind}"`);
continue;
}
if (c.kind === 'Attack' && c.damage == null && c.xDamagePerEnergy == null) {
if (c.kind === 'Attack' && c.damage == null && c.xDamagePerEnergy == null && c.damageFromCurrentBlock == null) {
issues.push(`${id}(${c.name}): kind=Attack인데 damage 없음 → 몬스터 드롭 라우팅 불가(방어/유틸이면 kind=Skill)`);
}
if (c.kind === 'Power' && !POWER_FIELDS.some((f) => c[f] != null)) {

View File

@@ -0,0 +1,82 @@
import { readFileSync } from 'node:fs';
const cards = JSON.parse(readFileSync('data/cards.json', 'utf8')).cards;
const rogueClasses = new Set(['rogue', 'thief', 'thiefmaster', 'assassin', 'hermit']);
const mapleSkillCards = {
DoubleStab: '더블 스탭',
LuckySeven: '럭키 세븐',
Haste: '헤이스트',
DarkSight: '다크 사이트',
FlashJump: '플래시 점프',
NimbleBody: '님블 바디',
SavageBlow: '새비지 블로우',
CriticalEdge: '크리티컬 엣지',
Steal: '스틸',
DaggerAcceleration: '대거 액셀레이션',
Karma: '카르마',
DaggerMastery: '대거 마스터리',
PhysicalTraining: '피지컬 트레이닝',
ShieldMastery: '실드 마스터리',
ThiefAgility: '시프 어질리티',
EdgeCarnival: '엣지 카니발',
MuspelHeim: '무스펠 하임',
MesoExplosion: '메소 익스플로젼',
DarkFlare: '다크 플레어',
PickPocket: '픽 파킷',
ShadowPartner: '쉐도우 파트너',
AdvancedDarkSight: '어드밴스드 다크 사이트',
IntoDarkness: '인투 다크니스',
Venom: '베놈',
Grid: '그리드',
RadicalDarkness: '래디컬 다크니스',
ShurikenBurst: '슈리켄 버스트',
WindTalisman: '윈드 탈리스만',
MarkOfAssassin: '마크 오브 어쌔신',
ShadowRush: '쉐도우 러쉬',
ShadowLeap: '쉐도우 리프',
ShadowBlink: '쉐도우 블링크',
JavelinMastery: '자벨린 마스터리',
JavelinAcceleration: '자벨린 액셀레이션',
CriticalThrow: '크리티컬 스로우',
AssassinPhysicalTraining: '피지컬 트레이닝',
TripleThrow: '트리플 스로우',
ShurikenChallenge: '슈리켄 챌린지',
HermitDarkFlare: '다크 플레어',
HermitShadowPartner: '쉐도우 파트너',
SpiritJavelin: '스피릿 자벨린',
HermitRadicalDarkness: '래디컬 다크니스',
HermitVenom: '베놈',
SkilledJavelin: '숙련된 표창술',
HermitAdrenaline: '아드레날린',
};
const errors = [];
for (const [id, expectedName] of Object.entries(mapleSkillCards)) {
if (!cards[id]) errors.push(`원본 스킬 카드 없음: ${id}`);
else if (cards[id].name !== expectedName) errors.push(`원본 스킬명 변경: ${id} (${cards[id].name} != ${expectedName})`);
}
const customCards = Object.entries(cards).filter(([id, card]) => rogueClasses.has(card.class) && !mapleSkillCards[id]);
if (customCards.length !== 78) errors.push(`도적 비스킬 카드 수 불일치: ${customCards.length} != 78`);
const names = new Map();
for (const [id, card] of Object.entries(cards)) {
if (!names.has(card.name)) names.set(card.name, []);
names.get(card.name).push(id);
}
const nonRogueNames = new Set(Object.values(cards).filter((card) => !rogueClasses.has(card.class) && card.class !== 'shiv').map((card) => card.name));
for (const [id, card] of customCards) {
const sameNameIds = names.get(card.name) || [];
if (sameNameIds.length > 1) errors.push(`비스킬 카드명 중복: ${id} ${card.name} (${sameNameIds.join(', ')})`);
if (nonRogueNames.has(card.name)) errors.push(`다른 직업 카드명 충돌: ${id} ${card.name}`);
}
console.log(`메이플 원본 스킬명 고정 ${Object.keys(mapleSkillCards).length}장 | 도적 비스킬 고유 이름 ${customCards.length}`);
if (errors.length > 0) {
for (const error of errors) console.error(`ERROR: ${error}`);
process.exitCode = 1;
} else {
console.log('RESULT: 도적 카드 이름 규칙 이상 0');
}