1 Commits

Author SHA1 Message Date
79e12d670c feat(charselect): 캐릭터 선택 UI 동작 배선 (초상화·흰 테두리·직업 정보·Art 클리핑)
사용자가 메이커에서 재구성한 CharacterSelectHud(SelectedClass/Eng/Status·
SelectedCharacterArt 신설, ThiefButton→BanditButton 개명)에 컨트롤러 연결.

RenderCharacterSelect:
- 버튼 클릭 시 해당 버튼 하위 Art의 ImageRUID를 SelectedCharacterArt로 복사(큰 그림)
- 선택 버튼 테두리 흰색(1,1,1,1)·비선택 디밍
- SelectedClass(한글)·SelectedClassEng(Warrior/Thief/Magician)·SelectedClassStatus
  ("직업군 · 모험가" + 설명) 갱신. 개행은 string.char(10) 연결(Lua 문자열 raw 개행 문법오류 회피)
- 각 Button에 MaskComponent(기본 Shape Rect) 런타임 부착 → Art를 Button 영역으로
  클리핑(Art 크기 불변, 넘치는 부분 숨김)
경로 교정: BindMenuButtons ThiefButton→BanditButton, StartNewGame Status→SelectedClassStatus.

UI(메이커 저작): CharacterSelectHud 재구성 + MageButton/BanditButton Art 위치 미세조정,
char-select 배경 에셋 01_blue_background_clean(sprite ac448e…) 추가.

산출물 재생성: SlayDeckController.codeblock + common.gamelogic.
검증: cbgap GAP 0, JS 미러 41/41, 인게임(초상화·테두리·텍스트·Art 클리핑) 확인.
(map01·타 UIGroup의 메이커 재직렬화 churn은 revert.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 08:44:47 +09:00
82 changed files with 3913 additions and 16828 deletions

View File

@@ -32,10 +32,10 @@
{
"@type": "script.SlayDeckController",
"Enable": true,
"Energy": 0,
"MaxEnergy": 3,
"Turn": 0,
"TweenEventId": 0
"Energy": 0.0,
"MaxEnergy": 3.0,
"Turn": 0.0,
"TweenEventId": 0.0
}
],
"@version": 1

View File

@@ -44,14 +44,11 @@ git pull
```
slaymaple/
├── data/ # 게임 데이터 단일 소스 (생성기가 읽어 주입). 맵은 정적 데이터 없음(절차 생성)
│ ├── cards.json # 카드 166장(1~3차 전직 계열별 + 저주) + 클래스별 시작 덱
│ ├── enemies.json # 적 18종(일반/정예/보스, 디버프 인텐트 + 외형 appearance)
│ ├── encounters.json # 맵별 몬스터 로스터(map01~05 × combat/elite/boss)
│ ├── cards.json # 카드 122장(클래스·2차전직별 + 저주) + 클래스별 시작 덱
│ ├── enemies.json # 적 12종(일반/정예/보스, 디버프 인텐트 포함)
│ ├── potions.json # 물약 6종 + 드랍률·슬롯·상점가
│ ├── relics.json # 유물 19종(StS 효과 × 메이플 장비) + 시작 유물 + 풀
│ ├── cardframes.json # 커스텀 카드 프레임 3종(전사/마법사/도적 × normal/unique/legend) + 보상 등급 가중치
│ ├── characters.json # 클래스별 초상화 RUID
│ ├── cards.xlsx # cards.json 왕복 편집용 엑셀(excel_to_cards.bat / cards_to_excel.bat)
│ └── camera.json # 맵별 카메라 설정값(줌·오프셋·고정 영역)
├── Global/ # 월드 전역 설정 · 공용 모델 · 게임로직
│ ├── common.gamelogic # SlayDeckController 부착 지점 (산출물)
@@ -69,19 +66,18 @@ slaymaple/
│ ├── MapCamera.codeblock # 맵별 카메라 적용
│ ├── PlayerLock.codeblock # 전투맵 플레이어 입력·이동 잠금
│ ├── LobbyNpc.codeblock # 로비 NPC 상호작용(근접·클릭)
── LobbyMobility.codeblock # 로비 이동·공격 해제 + 카메라 추종
│ └── Models/Monsters/ # 적 종별 모델 <enemyId>.model (산출물 — 외형·EnemyId 베이크)
── LobbyMobility.codeblock # 로비 이동·공격 해제 + 카메라 추종
├── 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(encounters.json 로스터 기반 종별 모델 인스턴스 배치) · rogue-map.mjs(절차 생성 JS 미러)+test
│ ├── deck/ # gen-slaydeck.mjs(★게임 전체 생성: 카드/덱·전투·맵노드·상점·유물·로비·메뉴 UI + SlayDeckController + common) · gen-cardhand.mjs
│ ├── map/ # gen-maps.mjs(맵 배경/타일) · gen-lobby-map.mjs(로비 맵+NPC) · gen-map-encounters.mjs(노드별 몬스터 그룹) · 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-monster-models.mjs(적 종별 .model) · gen-combat-monster.mjs(자기등록 codeblock) · freeze-turn-monsters.mjs(레거시 AI 정지) · lib/monster-model.mjs(공용 빌더)+test
│ ├── monster/ # gen-combat-monster.mjs(EnemyId 마커) · freeze-turn-monsters.mjs(필드 AI 정지)
│ ├── balance/ # sim-balance.mjs(전투 밸런스 몬테카를로 시뮬) · sim-balance.test.mjs
│ ├── verify/ # count·uimap·cbgap(카운트/UIGroup 매핑/재연결 GAP) · cardkinds(카드 kind↔효과) · cbprops(미선언 self 대입) · cbset(메서드 집합 무손실) · diffcheck(바이트동일)
│ ├── verify/ # count.mjs·uimap.mjs·cbgap.mjs(산출물 카운트/UIGroup 매핑/재연결 GAP 검증 — 경로 내장)
│ └── git/ # gitea-pr.mjs(UTF-8 안전 PR 생성/수정/머지 — RULES.md 참조)
├── ui/ # UIGroup 7종 — 메이커 저작(Default/Select/Lobby/Run/Deck/Popup/Toast)
├── docs/
@@ -102,13 +98,13 @@ slaymaple/
3직업 모두 Slay the Spire 2 차용 + 메이플 IP 재해석. 카드 덱 상세 설계는 [`docs/deck-concept.md`](docs/deck-concept.md) 참조.
- **⚔️ 전사 (탱커, Ironclad 차용, HP80)** — 2차 3종. **파이터**: 공격을 *연속*으로 내면 콤보가 쌓이고(비공격 카드 시 리셋) 콤보로 데미지 증가 = 브루저(콤보 어택·버서크·라이징 어택). **페이지**: 썬더/블리자드 **속성 차지** + 파워 가드. **스피어맨**: 피어스·아이언 월·하이퍼 바디 유지/관통형.
- **🗡️ 도적 (단검·독, Silent 차용, HP70)** — 표창 난사 / 독 / 교활·버림. **2차 어쌔신**(표창·독 압박·빠른 마무리)·**시프**(단검·드로우·연계) **3차 헤르밋**(어쌔신 심화)·**시프 마스터**(시프 심화). 도적 계열만 132장(Silent 완역 포트 + 공식 스킬 아이콘).
- **🔮 법사 (약체·게이지, Defect 차용, HP70)** — 2차 3종. **위자드(불·독)**: 독을 묻히고 *독 걸린 적에 불 카드 → 추가 데미지*(독뎀 시너지). **위자드(썬·콜)**: 오브로 썬더(다중 공격)·콜드(빙결=취약+피해), 오브 획득·다중 소모 운용. **클레릭**: 오브 없이 회복·버프 + 언데드엔 힐로 공격하는 보조 힐러.
- **⚔️ 전사 (탱커, Ironclad 차용)** — **파이터**: 공격을 *연속*으로 내면 콤보가 쌓이고(방어·파워 등 비공격 카드를 쓰면 콤보 리셋) 콤보로 데미지 증가 버프 = 브루저. **페이지**: 위협 디버프로 버티며 방어도 축적 → **바디 슬램(방어 비례 피해)** 카운터. **스피어맨**: 하이퍼바디·아이언월 유지/리치형.
- **🗡️ 도적 (단검·독, Silent 차용)** — 표창 난사 / 독 / 교활·버림. **어쌔신**(표창·크리·흡혈) / **시프**(단검 난타·독). *형 구현 완료(Silent 88장)*.
- **🔮 법사 (약체·게이지, Defect 차용)** — **위자드(불/독)**: 독을 묻히고 *독 걸린 적에 불 카드 → 추가 데미지*(독뎀 시너지). **위자드(썬/콜)**: 오브로 썬더(다중 공격)·콜드(빙결=취약+피해), 오브 획득·다중 소모 운용. **클레릭**: 오브 없이 회복·버프 + 언데드엔 힐로 공격하는 보조 힐러.
## 게임 프레임워크 현황
**StS2풍 덱빌더 로그라이크가 end-to-end로 완성**됐고, 이제 **로비 마을을 기점으로 반복 런**이 돕니다 (게임 시작 시 MainMenu 없이 바로 로비로 진입):
**StS2풍 덱빌더 로그라이크가 end-to-end로 완성**됐고, 이제 **로비 마을을 기점으로 반복 런**이 돕니다:
```
로비 맵(NPC 4종) → 모험가 NPC → 캐릭터 선택(전사/도적/마법사) → 절차 생성 맵(5막)
@@ -118,28 +114,26 @@ slaymaple/
게임 전체는 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작합니다. **UI는 메이커 저작**(7개 UIGroup: Default/Select/Lobby/Run/Deck/Popup/Toast)이고, 컨트롤러가 엔티티 경로(`/ui/<UIGroup>/<Hud>/...`)로 내용을 런타임 주입합니다. 생성기 `tools/deck/gen-slaydeck.mjs`**`SlayDeckController.codeblock` + `common.gamelogic`만 생성**(`.ui` 미접근, 결정적 출력 — `RULES.md` 참조). 게임 데이터는 **`data/*.json`**, 맵 구조는 **런타임 절차 생성**(`GenerateMap` Lua ↔ `tools/map/rogue-map.mjs` JS 미러).
### 구현된 기능 (배포 퀄리티 P1~P15+, PR #34~#104)
### 구현된 기능 (배포 퀄리티 P1~P15, PR #34~#57)
| 영역 | 내용 |
|---|---|
| **로비 마을** | 전용 물리 맵 `lobby.map`(마을 배경). **NPC 4종 월드 엔티티** — 모험가(런 시작)·사서(카드 도감)·상인(영혼 상점)·안내원(게시판). 근접 시 머리 위 마크 + `↑`**또는 직접 클릭**으로 상호작용. **이동·공격 모션은 로비 맵에서만** 풀림(전투맵은 잠금), 카메라는 로비에서 **플레이어 추종**(전투맵은 고정) |
| **캐릭터·전직** | 시작 시 **전사(HP80)/도적(HP70)/마법사(HP70)** 3종 선택(**초상화·직업 설명·선택 테두리 강조** 캐릭터 선택 UI), 클래스별 시작 덱. 보스 클리어 시 [유물] vs [**전직**] — 전사→파이터/페이지/스피어맨, 법사→위자드(불·독)/위자드(썬·콜)/클레릭 (2차 3종씩), **도적→어쌔신·시프(2차) → 헤르밋·시프 마스터(3차)**. 전직 시 대표 카드 지급, 전용 카드는 해당 계열 풀만 획득 |
| **카드 전투** | 에너지 3·드로우·**드래그 사용**(공격=적에 드롭, 스킬/파워=위로 스윕). 카드 **166** — kind **Attack(59)/Skill(74)/Power(31)/Status(2)**. kind↔효과 정합성 정적 검증(`cardkinds.mjs`). 메커니즘: 다단히트·방어 무시·자가 디버프·드로·회복·**전체 공격(AoE)**·**독(DoT)**·**retain**(턴 종료 손패 유지)·**sly discard**(버림 트리거) |
| **도적 카드 공용 효과** | 카드 효과를 **카드명 하드코딩 대신 `data/cards.json` 공용 필드**로 표현(재사용). **불가침**·**x-cost**(에너지 비례 피해/약화)·드로우 수 비례 데미지·**다음 스킬 반복**·**처치 보상/반복**·카드 설명 **키워드 하이라이트**·드로우 연동(`drawSkillBlock`·`drawPoison`)·독 버스트·랜덤 타깃 등. **Lua + JS 미러 양쪽 구현**. 필드 사전 [`docs/card-effect-fields.md`](docs/card-effect-fields.md) |
| **캐릭터·전직** | 시작 시 **전사(HP80)/도적(HP70)/마법사(HP70)** 3종 선택, 클래스별 시작 덱. 보스 클리어 시 [유물] vs [**2차 전직**] — 각 클래스 3종(전사→파이터/페이지/스피어맨, 법사→위자드불독/위자드썬콜/클레릭, 도적→Shiv/Poison/Trickster). 전용 카드는 해당 클래스 풀만 획득 |
| **카드 전투** | 에너지 3·드로우·**드래그 사용**(공격=적에 드롭, 스킬=위로 스윕). 카드 **122** — kind **Attack/Skill/Power/Status**. 메커니즘: 다단히트·방어 무시·자가 디버프·드로·회복·**전체 공격(AoE)**·**독(DoT)**·**retain**(턴 종료 손패 유지)·**sly discard**(버림 트리거) |
| **버프/디버프** | 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 영구 저장** |
| **승천(Ascension)** | A1~A10 누적 모디파이어(적 강화·시작 HP 감소·보상 감소). UserDataStorage 유저별 영구 저장, 런 클리어 시 다음 단계 해금 |
| **멀티 act** | **5막** 진행(보스 클리어→다음 막 텔레포트, 맵·인카운터 변경, 적 스케일 `1+(막-1)*0.45`), 5막 클리어 시 런 종료 |
| **경제** | 화폐 표기 **메소**(코인 아이콘), 카드/유물/물약 메소 가격. 내부 식별자는 Gold 유지 |
| **밸런스 시뮬** | `tools/balance/sim-balance.mjs` — 전투 규칙 JS 미러(몬테카를로) + `tools/map/rogue-map.mjs`(맵 생성 미러) + node 단위테스트(현 97종) |
| **밸런스 시뮬** | `tools/balance/sim-balance.mjs` — 전투 규칙 JS 미러(몬테카를로) + `tools/map/rogue-map.mjs`(맵 생성 미러) + node 단위테스트 |
> ⚠️ 수치(적 스탯·경제·승천 배율)는 1차 조정 상태입니다. 정밀 밸런싱은 `sim-balance.mjs`로 검증하며 진행합니다.
> 도적 계열 카드 132장은 STS Silent 완역 포트 + **공식 스킬 아이콘 적용 완료**, rogue 1차 + 어쌔신/시프(2차) + 헤르밋/시프 마스터(3차)로 재편. 남은 작업은 카드명 메이플 재서사·멀티플레이어 전제 카드 싱글 정리 — [`docs/deck-concept.md`](docs/deck-concept.md)·[`docs/bandit-card-audit.md`](docs/bandit-card-audit.md) 참조.
> 도적(Silent) 카드 88장은 STS Silent 완역 포트 + **공식 스킬 아이콘 적용 완료**(PR #73). 남은 작업은 카드명 메이플 재서사(어쌔신/시프)·멀티플레이어 전제 카드 싱글 정리 — [`docs/deck-concept.md`](docs/deck-concept.md) 참조.
### 유용한 스크립트 호출
`/common` 엔티티(또는 Play Test 컨텍스트)에서:
@@ -149,14 +143,14 @@ local c = _EntityService:GetEntityByPath("/common").SlayDeckController
c:OnLobbyNpcInteract("run") -- 모험가(런 시작) / "codex"(도감) / "shop"(영혼상점) / "board"(게시판)
c:ShowLobby() -- 로비 맵 복귀 + 상태 초기화
-- 런
c:SelectClass("warrior") -- "warrior" / "rogue" / "magician"
c:SelectClass("warrior") -- "warrior" / "bandit" / "magician"
c:StartNewGame() -- 캐릭터 선택 → 런 시작(map01 텔레포트)
c:PickNode("r1c2") -- 맵 노드 선택(절차 생성 그리드 id) / "boss"
c:PlayCard(1) -- 손패 slot 카드 사용
c:EndPlayerTurn() -- 턴 종료 → 적 턴 → 다음 턴
c:PickReward(1) -- 보상 카드 1택(0=건너뛰기)
c:BuyCard(1) / c:BuyRelic() / c:BuyPotion() -- 상점 구매(메소)
c:SetJob("fighter") -- 전직 (보스 보상 화면) — 2차: fighter/page/spearman·firepoison/icelightning/cleric·assassin/thief, 3차: hermit/thiefmaster
c:SetJob("fighter") -- 전직 (보스 보상 선택 화면)
c:AdjustAscension(1) -- 메뉴에서 승천 단계 +1
```
@@ -182,11 +176,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-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/monster/gen-combat-monster.mjs # 몬스터 EnemyId 마커
```
> 산출물 검증은 내용 출력 없이 카운트만: `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)).
> 산출물 검증은 내용 출력 없이 카운트만: `node tools/verify/count.mjs <ui|cb|common> <regex>...` (자세한 가드는 [`RULES.md`](RULES.md)).
---
@@ -195,22 +187,17 @@ node tools/map/gen-map-encounters.mjs # encounters.json 로스터 기반 맵
현재 게임 전체 로직이 `SlayDeckController` 단일 codeblock에 모여 있습니다. 초기 설계의 3분할(`SlayCardCatalog`/`SlayRunState`/`SlayCombatManager`)은 **기능적으로 모두 구현**됐으나 아직 한 컴포넌트 안에 있습니다. 맵 NPC·카메라·입력 잠금 등 **맵 단위 동작은 별도 codeblock**(LobbyNpc/LobbyMobility/MapCamera/PlayerLock/CombatMonster)으로 분리해 각 맵 루트/엔티티에 부착합니다. 카드/적/맵/유물/프레임/카메라 데이터는 `data/*.json`로 외부화돼 있습니다. **2026-06-17**: UI를 단일 `DefaultGroup`에서 7개 UIGroup(Select/Lobby/Run/Deck 등)으로 분리해 **메이커 저작으로 전환** — 생성기는 더 이상 `.ui`를 만들지 않고, 컨트롤러가 새 UIGroup 경로로 재연결됨(옛 UI emit `hud/*`·`gen-cardhand``tools/deck/legacy/` 휴면). 재연결 무결성은 `tools/verify/cbgap.mjs`(GAP 0)로 검증.
> ⚠️ **전투 규칙과 맵 생성은 Lua(gen-slaydeck 내장)와 JS 미러(sim-balance/rogue-map)로 이중 구현**입니다. 한쪽을 고치면 반드시 다른 쪽도 동기화하고 테스트하세요(`RULES.md` §6).
> ⚠️ **카드 `kind`는 효과와 반드시 일치**해야 합니다 — 데미지=`Attack`, 방어/유틸=`Skill`, 지속효과=`Power`. 안 맞으면 런타임 에러 없이 *사용 불가/무효과 死카드*가 됩니다(2026-06-30 Defend·Rage 사고). 새 효과 필드는 `docs/card-effect-fields.md` 등록 + Lua/JS 양쪽 핸들러 구현. 정적 검증 `node tools/verify/cardkinds.mjs`(`RULES.md` §9). cb Lua 지역변수는 의미명 사용(`RULES.md` §8).
---
## 향후 개선 계획 (후속 후보)
- [x] 전투 루프 · 런 루프 · 절차 생성 맵 · 상점/휴식/유물 방 · 유물 19종 · 물약 · 버프/디버프 · Power · 전직(전사/법사/도적 2차) · 승천+개인 저장 · 전투 모션 · 커스텀 프레임 · **반복 런·로비 맵·NPC·영혼·메소·카메라 추종 (P1~P15 완료)**
- [x] **UI 메이커-저작 전환** — 단일 DefaultGroup → 7개 UIGroup 분리, 생성기 UI 저작 폐기(`tools/deck/legacy/`), 컨트롤러 경로 재연결(cbgap GAP 0) (2026-06-17)
- [x] **시작 로비 직행 · 캐릭터 선택 UI · 디버그 치트 · map01 로스터 (2026-06-18)** — 게임 시작 시 MainMenu 없이 곧장 로비 진입(MainMenu는 추후 싱글/멀티/종료 메뉴로 재지정); 캐릭터 선택 화면 초상화·직업 설명·선택 테두리·Art 클리핑(MaskComponent) 배선; 디버그 단축키 Ctrl+Shift+C(카드 picker)·Ctrl+Shift+E(체력+에너지 전체 회복); map01 몬스터 18종 로스터(랜덤 행동)
- [x] **컨트롤러 관심사별 모듈 분리 · 코드 규칙 (2026-06-26, #94)** — SlayDeckController를 `cb/*.mjs` 20모듈로 분리(런타임은 단일 codeblock 유지), 변수명 의미화, 검증 `cbset.mjs`(집합 무손실)·`cbprops.mjs`(미선언 self)
- [x] **도적 계열 대개편 + 3차 전직 · 카드 공용 효과 (2026-06-23~30, #82~#99)** — Silent 포트를 rogue 1차 + 어쌔신/시프(2차) + 헤르밋/시프 마스터(3차)로 재편, 카드 효과를 카드명 하드코딩 대신 `cards.json` 공용 필드로(`docs/card-effect-fields.md`), 카드 **166장**
- [x] **코드리뷰 버그수정 + kind↔효과 규칙 (2026-06-29~30, #96·#102)** — 게임버그 6·시뮬 충실도 3·설명 2 수정(Defend kind Attack→Skill·Rage Power→Attack 포함), kind↔효과 정적 검증 `cardkinds.mjs`, 카드 왕복 편집 엑셀(#93)
- [ ] **도적 카드명 재서사·설명 한글화** — Silent 직역 카드명을 어쌔신/시프 메이플 스킬명으로 재서사(아이콘은 적용 완료), 2·3차 전직 설명 한글화
- [x] **UI 메이커-저작 전환** — 단일 DefaultGroup → 7개 UIGroup 분리, 생성기 UI 저작 폐기(`tools/deck/legacy/`), 컨트롤러 경로 재연결(cbgap GAP 0), MainMenu→로비→run NPC→charselect 부트 흐름 (2026-06-17)
- [ ] **도적 카드 아이콘** — Silent 88장에 실 스킬 아이콘(image/fx) 할당, 2차 전직 설명 한글화
- [ ] **런 이어하기** — 진행 중 런 직렬화 저장(UserDataStorage 확장, 메뉴 "이어하기" 활성화)
- [ ] **카드 제거/업그레이드** — 상점 카드 제거 슬롯, 휴식 노드에서 카드 강화
- [ ] **이벤트 노드(?)** — 랜덤 텍스트 이벤트(선택지·리스크/리워드)
- [ ] **3차 전직 — 전사·법사 확장** (도적은 완료: 헤르밋·시프 마스터), 후반 막 보상으로
- [ ] **3차 전직** — 후반 막 보상으로 확장
- [ ] **궁수 등 추가 클래스** — 캐릭터 선택 슬롯 확장
- [ ] **정밀 밸런싱** — 첫 인카운터 승률 완화·직업별 카드 효율 튜닝(`sim-balance.mjs` 리포트 기반)
- [ ] **상점 보장 규칙** — 막당 상점 최소 1회 등장

View File

@@ -16,7 +16,6 @@ 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` |
@@ -24,7 +23,7 @@ Claude Code는 `CLAUDE.md`가 이 파일을 임포트하므로 자동 적용된
- `.claude/settings.json`의 permissions.deny가 위 파일의 Read/Edit/Write 도구 사용을 차단한다 (이 저장소를 열면 자동 적용). deny는 **glob**`ui/*.ui`·`map/*.map`·`RootDesk/MyDesk/*.codeblock`·`Global/common.gamelogic`·`Global/SectorConfig.config`. 따라서 **메이커 저작 codeblock/UI**(`Monster`·`MonsterAttack`·`PlayerAttack`·`PlayerHit`·`UIPopup`·`UIToast`.codeblock, **그리고 모든 `ui/*.ui`** — UI는 6개 UIGroup으로 메이커 저작)**도** Read/Edit 금지 — 이들은 생성기가 없으니 **메이커에서** 편집한다(텍스트 도구로 X). codeblock은 한 줄짜리 JSON이라 Read 시 토큰 폭발.
- **게임 로직 수정** = `tools/deck/gen-slaydeck.mjs`(오케스트레이터) + `tools/deck/cb/*.mjs`(codeblock Lua) 또는 `data/*.json`(데이터) 수정 → 재생성(`SlayDeckController.codeblock`+`common.gamelogic`만, **`.ui` 미접근**) → 통째로 커밋. **UI 수정 = 메이커에서**(생성기는 UI를 안 만든다).
- **codeblock 메서드(Lua)는 관심사별 모듈** `tools/deck/cb/*.mjs`(boot·screens·npc·navigation·layout·combat·hand·deckview·items·map·shop 등 20종 — 화면전환=`screens`·NPC=`npc`·포지션=`navigation`(월드 텔레포트)/`layout`(UI 슬롯 배치). 새 메서드는 관심사에 맞는 모듈에 작성하고, 한 모듈이 비대해지면 분할한다. 횡단 관심사를 한 모듈에 몰아넣지 않는다). **공유분**: 상수·데이터·lua 테이블 = `tools/deck/lib/{ui-helpers,data,codeblock}.mjs`(cb가 import — `MAX_MONSTERS`=4 등). prop 103개는 오케스트레이터 `writeCodeblocks`에 유지. 특정 메서드 수정은 `cb/<name>.mjs`만(의존: orchestrator→cb→lib 단방향). **cb 모듈은 원본 메서드 순서 보존이 바이트동일 조건**. **UI emit(옛 `hud/*.mjs` 15종·`gen-cardhand.mjs`)은 `tools/deck/legacy/`로 이관 — 휴면(생성기 미사용)**: UI가 메이커 저작이라 생성기가 안 만든다. (롤백용 `legacy/upsert-ui.mjs`는 직접 실행 시에만 옛 `DefaultGroup.ui`를 재생성.)
- **codeblock 메서드(Lua)는 기능별 모듈** `tools/deck/cb/*.mjs`(boot·state·combat·hand·deckview·items·map·shop 등 17종). **공유분**: 상수·데이터·lua 테이블 = `tools/deck/lib/{ui-helpers,data,codeblock}.mjs`(cb가 import — `MAX_MONSTERS`=4 등). prop 103개는 오케스트레이터 `writeCodeblocks`에 유지. 특정 메서드 수정은 `cb/<name>.mjs`만(의존: orchestrator→cb→lib 단방향). **cb 모듈은 원본 메서드 순서 보존이 바이트동일 조건**. **UI emit(옛 `hud/*.mjs` 15종·`gen-cardhand.mjs`)은 `tools/deck/legacy/`로 이관 — 휴면(생성기 미사용)**: UI가 메이커 저작이라 생성기가 안 만든다. (롤백용 `legacy/upsert-ui.mjs`는 직접 실행 시에만 옛 `DefaultGroup.ui`를 재생성.)
- 리팩터 시 **출력 바이트-동일 검증**: `node tools/deck/gen-slaydeck.mjs``node tools/verify/diffcheck.mjs [ref]`(워킹트리 vs ref(기본 HEAD) 줄바꿈 정규화 비교 — 산출물 경로를 명령줄에 노출 안 해 deny 회피). 산출물 ` M`은 보통 autocrlf churn이니 `git checkout --`로 복원.
- **UI 전면 메이커 저작 (2026-06-17~)**: 단일 `DefaultGroup`을 7개 UIGroup으로 분리 — `DefaultGroup`(MainMenu+월드조작), `SelectUIGroup`(charselect/job), `LobbyUIGroup`(lobby/board/soulshop), `RunUIGroup`(combat/map/shop/rest/treasure/reward/cardhand/deck), `DeckUIGroup`(덱 도감), `PopupGroup`·`ToastGroup`. 컨트롤러(`cb/*.mjs`)는 엔티티 **경로**(`/ui/<UIGroup>/<Hud>/...`)로 텍스트·이미지·표시숨김·상태기반 위치/크기/색을 **런타임 주입**(레이아웃=메이커, 내용=컨트롤러 — 메이커가 이 경로 유지 필수). 몬스터 슬롯 = `RunUIGroup/CombatHud/MonsterStatus{1..4}`(자식 Name·Hp·Intent·HpBarFill·Buffs·BlockBadge·TargetMarker; TargetFrame 없음). **부트 흐름**: `OnBeginPlay`→MainMenu→(`MainMenu/NewGameButton`)→로비→run NPC(`OnLobbyNpcInteract` id=="run")→charselect→런. **재연결 검증**: `node tools/verify/cbgap.mjs`(cb 참조 경로↔.ui GAP 0이어야) + 재생성 후 `git status -- ui/` 변경 0(생성기 .ui 미접근 증명). 섹션→UIGroup 일괄 remap 마이그레이션은 `tools/deck/reconnect-ui-paths.mjs`(멱등). UIGroup별 .ui 분포 확인은 `tools/verify/uimap.mjs`.
- **머지 충돌(gen-slaydeck.mjs)**: 다른 브랜치가 단일체를 수정해 충돌나면, 그쪽 버전(`git checkout --theirs tools/deck/gen-slaydeck.mjs`)을 취해 **콘텐츠 마커 기반으로 재모듈화**(라인인덱스 X — 줄 추가에 안전·export 이름 자동 파생·`const x=[]` 직전 전문 상수 walk-back 포함) 후 `node tools/verify/diffcheck.mjs origin/main`으로 ui·codeblock 바이트-동일 확인(손실 0 증명). codeblock 메서드·patchCommon은 오케스트레이터 잔류라 그쪽 변경은 자동 보존됨.
@@ -32,10 +31,9 @@ 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/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/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/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 고정
@@ -67,8 +65,6 @@ grep -c "CalcPlayerAttack" RootDesk/MyDesk/SlayDeckController.codeblock
- 제목/본문은 UTF-8 spec JSON 파일로 작성 후 `create <spec.json>` / `merge <번호>`.
- PR 제목과 본문은 한국어로 작성한다.
- 산출물 재생성 커밋은 소스 변경 커밋과 분리하거나, 메시지에 "산출물 재생성"을 명시.
- **PR 머지 후 브랜치 삭제**: 머지된 `feature/*`·`docs/*` 브랜치는 로컬·원격 모두 삭제한다. 삭제 전 `git merge-base --is-ancestor origin/<브랜치> origin/main`로 완전 머지 확인(종료코드 0=완전 머지 → 삭제 가능). main에 없는 커밋이 남은 브랜치와 `codex/*` 등 작업 중 브랜치는 보존한다.
- **⚠️ main 머지 충돌 시 "머지 전체 revert" 금지 (타인 작업 유실 방지)**: 작업 브랜치에 `git merge main`(또는 origin/main) 했다가 충돌·문제가 나도 **그 머지 커밋을 통째로 `git revert` 하지 말 것.** main에 먼저 들어간 타인의 작업이 collateral로 전부 사라진다. 대신 **소스 충돌만 해소**하고 산출물(codeblock 등)은 **재생성**한다. 충돌이 산출물뿐이면 `git checkout --theirs`/재생성으로 끝. (2026-06-30 사고: codex `#98/#99`가 main 머지 후 그 머지를 revert해 `#96`의 버그수정 11개를 전부 날림 → 다시 재통합해야 했다. 복구는 `git diff <pre-merge> <내브랜치> -- <소스> | git apply --3way` 로 소스만 재적용 후 재생성하면 codex 변경과 충돌 없이 양립.)
## 5. 메이커(MSW) 연동 주의
@@ -90,18 +86,3 @@ grep -c "CalcPlayerAttack" RootDesk/MyDesk/SlayDeckController.codeblock
- UI 텍스트에서는 정수값인 숫자에 `.0`을 붙이지 않는다. `1.0/1.0`이 아니라 `1/1`처럼 표시한다.
- 생성기 내 Lua UI 코드에서 number 또는 숫자 문자열을 텍스트에 붙일 때는 `FormatNumber` 같은 포맷 헬퍼를 우선 사용한다.
- 소수부가 플레이어에게 의미 있을 때만 소수점 표기를 유지한다.
## 8. codeblock 변수명
- cb(`tools/deck/cb/*.mjs`)의 Lua 지역변수는 **의미가 드러나는 이름**으로 작성한다(`e``entity`, `n``count`, `m``monster`, `lp``localPlayer`, `s``soulPoints`, `tr``transform`). `a`/`b`/`c` 같은 무의미 단일문자 변수는 금지.
- 단, 순수 반복 인덱스 `i`/`j`/`r`/`c`는 관용상 허용한다.
- 새 cb 메서드를 작성하거나 기존 메서드를 손댈 때 이 규칙을 적용한다(대규모 일괄 개명은 별도 작업으로).
## 9. 카드 데이터 규칙 (kind ↔ 효과 일치)
새 카드를 추가/수정할 때 `data/cards.json``kind`는 카드의 효과·사용 메커니즘과 **반드시 일치**해야 한다. 안 맞으면 카드가 **사용 불가**거나 **재생 시 아무 효과 없는 死카드**가 된다(런타임 에러도 안 나고 sim 테스트도 못 잡음 — 정적 검증 필수).
- **`ResolveCardDrop` 사용 라우팅이 kind별로 다름**: `Attack`=몬스터 위에 드롭(`FindMonsterAtTouch>0` 필요)·`Skill`/`Power`=위로 스윕(`ui.y>-180``Status`=unplayable. → **block·디버프·드로우 등 유틸만 있고 데미지가 없는 카드를 `Attack`으로 두면 위로 스윕으로 사용할 수 없다**(2026-06-30 아이언 바디 사고: block만 있는 방어카드가 Attack이라 전사 시작덱 4장이 먹통 → Skill로 수정).
- **`PlayCard``Power` 분기는 PlayerPowers 등록만 하고 `damage`/`aoe`를 무시**한다. → 데미지 카드=`Attack`, 방어/유틸=`Skill`, 지속효과=`Power`(단 `powerEffect` 또는 지속/온플레이 power 필드 — `turnStart*`·`dex`·`thorns`·`intangible`·`attackPoison`·`drawDamage`·`shivX`·`cardPlayed*` 등 — 이 있어야 함). Power인데 power 효과 필드가 없으면 死카드(2026-06-30 분노 사고: `damage:4/aoe`만 있어 Power 분기서 무시됨 → kind Power→Attack으로 기능화).
- 새 효과 필드는 `docs/card-effect-fields.md` 사전에 등록하고 Lua(`tools/deck/cb/*.mjs`) + JS 미러(`tools/balance/sim-balance.mjs`) **양쪽에 핸들러 구현**(§6). 한쪽만 있으면 게임↔시뮬 드리프트.
- **검증: `node tools/verify/cardkinds.mjs`** — kind↔효과 위반(Attack-무데미지 / Power-무효과 / 미지원 kind)을 정적 검출(이상 0 = exit 0). 카드 추가/수정 후 반드시 실행. (관련 가드: 미선언 `self.X` = `cbprops.mjs`, UI 경로 = `cbgap.mjs`, 이중구현 = `sim-balance.test.mjs`.)

View File

@@ -1,231 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,229 +0,0 @@
{
"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

@@ -1,229 +0,0 @@
{
"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

@@ -1,229 +0,0 @@
{
"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

@@ -1,229 +0,0 @@
{
"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

@@ -1,233 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,230 +0,0 @@
{
"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

@@ -1,232 +0,0 @@
{
"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

@@ -1,229 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,230 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,231 +0,0 @@
{
"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

@@ -1,230 +0,0 @@
{
"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

@@ -1,7 +0,0 @@
@echo off
setlocal
chcp 65001 >nul
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0tools\cards\cards_excel.ps1" export
echo.
echo Press any key to close this window.
pause >nul

View File

@@ -10,7 +10,7 @@
"unique": "f5def2e8022b4e59a17d3c16414034fe",
"legend": "cff71f2e472041ce80c6fbd296f42e2d"
},
"rogue": {
"bandit": {
"normal": "9487b06867bc46269ed1d855420f457f",
"unique": "b3081fb2fb1445fa90b12b01481a78ef",
"legend": "c357d2daf31a489d95b8fa47e50dd879"
@@ -19,21 +19,17 @@
"classToFrame": {
"warrior": "warrior",
"fighter": "warrior",
"crusader": "warrior",
"page": "warrior",
"knight": "warrior",
"spearman": "warrior",
"magician": "magician",
"firepoison": "magician",
"icelightning": "magician",
"cleric": "magician",
"curse": "rogue",
"shiv": "rogue",
"rogue": "rogue",
"assassin": "rogue",
"hermit": "rogue",
"thief": "rogue",
"thiefmaster": "rogue"
"bandit": "bandit",
"curse": "bandit",
"shiv": "bandit",
"poisoner": "bandit",
"trickster": "bandit"
},
"rewardWeights": {
"normal": 70,

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{
"portraits": {
"warrior": "28c88fdc5ab44f34a8b3fc1e19d4ce78",
"warrior": "28c88fdc5ab44f34a8b3fc1e19d4ce78",
"magician": "3b9ea1f066a744bb859df47fef817277",
"rogue": "efa920e58d31426486ef974106e7dc8b"
"bandit": "efa920e58d31426486ef974106e7dc8b"
}
}

View File

@@ -1,7 +0,0 @@
{
"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,8 +7,7 @@
{ "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": "정예 슬라임",
@@ -18,8 +17,7 @@
{ "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": "슬라임 킹",
@@ -30,8 +28,7 @@
{ "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": "주황버섯",
@@ -41,8 +38,7 @@
{ "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": "파란버섯",
@@ -52,8 +48,7 @@
{ "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": "돼지",
@@ -62,8 +57,7 @@
{ "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": "초록버섯",
@@ -72,8 +66,7 @@
{ "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": "빨간 달팽이",
@@ -82,8 +75,7 @@
{ "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": "나무토막",
@@ -92,8 +84,7 @@
{ "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": "머쉬맘",
@@ -105,8 +96,7 @@
{ "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": "변형된 달팽이",
@@ -117,8 +107,7 @@
{ "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": "킹 슬라임",
@@ -129,8 +118,7 @@
{ "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": "문어",
@@ -139,8 +127,7 @@
{ "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": "카파 드레이크",
@@ -150,8 +137,7 @@
{ "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": "주니어 네키",
@@ -160,8 +146,7 @@
{ "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": "주니어 부기",
@@ -170,8 +155,7 @@
{ "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": "다일",
@@ -182,8 +166,7 @@
{ "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": "마노",
@@ -194,8 +177,7 @@
{ "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,7 +0,0 @@
# 공격 중독
`attackPoison`은 전투 동안 유지되는 공용 카드 효과 필드입니다.
- 공격 카드가 실제 체력 피해를 주면 대상에게 지정된 수치만큼 중독을 부여합니다.
- 광역 공격은 피해를 받은 각 적에게 중독을 부여합니다.
- 현재 Thief Master와 Hermit의 `베놈`이 이 효과를 사용합니다.

View File

@@ -1,72 +0,0 @@
# 도적 카드 구성 및 밸런스 기록
도적 계보의 카드 역할, 직업 이동 금지 대상, 공용 효과 필드를 정리합니다.
## 직업별 컨셉
- `rogue`: 시작 카드, 1차 스킬, 기초 공격·회피·방어
- `thief`: 단검 난타, 교활, 버리기, 중독의 시작
- `thiefmaster`: 교활·버리기 연계 완성, 광역 난타, 중독 증폭
- `assassin`: 표창 생성, 표창 연속 공격, 표창 비용·피해 보조
- `hermit`: 표창 보존·광역화·지속 생성 등 표창 빌드 완성
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`
- `turnStartDraw`, `turnStartDiscard`
- `nextTurnBlock`, `nextTurnDraw`, `nextTurnKeepBlock`, `nextTurnAttackMultiplier`, `nextTurnCopies`, `nextTurnSelectHandCard`
- `damagePerOtherHandCard`, `damagePerAttackPlayedThisTurn`, `damagePerDiscardedThisTurn`, `damagePerSkillInHand`, `otherHandAtLeast`, `bonusHitsWhenOtherHandAtLeast`
- `gainEnergy`, `drawUntilHandSize`, `drawPerDiscarded`, `cardPlayedBlock`, `blockGainMultiplier`, `blockPerDamageDealtThisTurn`, `nextSkillCostZero`, `skillCostReductionThisTurn`
- `firstCardDamageBonus`
- `drawDamage`, `drawPoison`, `shivDamageBonus`, `firstShivDamageBonus`, `shivRetain`, `shivAoe`, `attackDamageVsWeakMultiplier`, `poisonHits`, `poisonRandomTargets`, `skillSlyOnPlay`, `extraPoisonTicks`, `poisonApplicationBurstEvery`, `poisonApplicationBurstDamage`
## 중복 제거 및 보정
- 삭제: `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

@@ -1,164 +0,0 @@
# Card Effect Fields
This file tracks the shared data fields used by `data/cards.json`.
The goal is to keep card behavior reusable instead of hardcoding one-off card names.
## 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
- `drawDamage`: damage dealt when a card is drawn
- `blockPerDamageDealtThisTurn`: gain block equal to damage dealt this turn
- `shivDamageBonus`: bonus damage for all Shivs
- `firstShivDamageBonus`: bonus damage for the first Shiv each turn
- `attackDamageVsWeakMultiplier`: multiplier when the attack hits Weak targets
- `useAllEnergy`: treat the card as spending all available energy
- `xDamagePerEnergy`: scale attack damage by energy spent
- `xWeakPerEnergy`: scale Weak applied by energy spent
## Block and utility
- `block`: gain block
- `cardPlayedBlock`: gain block whenever a card is played
- `blockGainMultiplier`: multiplier for block gained this turn
- `hits`: multi-hit count
- `aoe`: hit all enemies
- `pierce`: ignore block
- `draw`: draw cards immediately
- `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
- `gainEnergy`: gain energy immediately
- `strength`: gain Strength
- `dex`: gain Dexterity
- `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
- `weak`: apply Weak
- `vuln`: apply Vulnerable
- `poison`: apply Poison
- `poisonHits`: apply poison multiple times
- `poisonRandomTargets`: spread poison applications across random alive enemies
- `poisonIfTargetPoisoned`: apply poison only if the target is already poisoned
- `poisonApplicationBurstEvery`: trigger a burst every N poison applications
- `poisonApplicationBurstDamage`: burst damage when the poison application threshold is reached
- `skillSlyOnPlay`: make a played Skill card count as sly when it is later discarded
- `turnHandSlyCount`: mark up to N other Skill cards in hand as sly for this turn
- `attackPoison`: apply poison when attack damage is dealt
- `intangible`: reduce incoming damage to 1 for the duration
- `endTurnDexLoss`: lose Dexterity at end of turn
- `combatCostReductionOnPlay`: reduce this card's cost each time it is played this combat
- `enemyStrengthLossThisTurn`: reduce enemy Strength for the rest of the turn
- `affectsAllEnemies`: apply the card's debuffs to every alive enemy
- `removeEnemyBlock`: clear enemy block when the card resolves
- `removeEnemyArtifact`: consume enemy Artifact when the card resolves
`poison` deals damage at enemy turn start and then decreases by 1.
## Shivs and discard
- `discard`: discard a chosen number of cards from hand
- `discardAll`: discard the whole hand
- `drawPerDiscarded`: draw one extra card per discarded card
- `addShiv`: create Shiv cards
- `addShivPerDiscard`: create one Shiv per discarded card
- `shivRetain`: Shiv cards are retained at end of turn
- `shivAoe`: Shiv cards hit all enemies for the turn
- `sly`: trigger on discard
- `retain`: keep the card at end of turn
## Powers and turn effects
- `powerEffect: "strengthPerTurn"`
- `powerEffect: "energyPerTurn"`
- `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
## Next turn planning
- `nextTurnBlock`: gain block next turn
- `nextTurnDraw`: draw extra cards next turn
- `nextTurnKeepBlock`: keep block next turn
- `nextTurnAttackMultiplier`: attack multiplier next turn
- `nextTurnCopies`: copy a chosen card next turn
- `nextTurnSelectHandCard`: choose a card from the current hand for next turn copies
- `nextTurnSelectPrompt`: prompt text for selection UI
- `nextSkillRepeatCount`: repeat the next Skill's effect
- `nextSkillCostZero`: make the next Skill cost 0
- `skillCostReductionThisTurn`: reduce Skill costs this turn
## Misc
- `innate`: place the card in the opening hand
- `playableWhenDrawPileEmpty`: only playable when the draw pile is empty
- `exhaust`: exhaust after use
- `unplayable`: cannot be played
- `curse`: curse card
- `token`: token card
- `endTurnDamage`: damage if the card remains in hand at end of turn
## Rules
- Prefer shared fields over card-specific branches.
- Reuse the same field name for the same behavior.
- Add a new shared field before adding more special-case card logic.

View File

@@ -1,5 +0,0 @@
# 카드 사용 시 피해
`cardPlayedDamage`는 카드를 사용할 때마다 현재 대상에게 체력을 직접 깎는 공용 효과입니다. 방어도는 무시하고, 같은 필드를 다른 카드에도 그대로 붙여 재사용할 수 있습니다.
`cardPlayedRandomDamage`는 같은 시점에 살아 있는 적 하나를 랜덤으로 골라 체력을 직접 깎습니다. `Strangle``SerpentForm` 같은 카드가 이 계열을 씁니다.

View File

@@ -1,39 +0,0 @@
# Codex Workflow
이 저장소에서 작업할 때는 토큰과 변경량을 아끼는 쪽을 기본으로 둔다.
## 작업 원칙
- 이미 확인한 사실은 다시 읽지 않는다.
- 같은 내용을 통째로 지우고 새로 쓰지 않는다.
- 수정은 가능한 한 `apply_patch`로 섹션 단위만 한다.
- 문서는 전체 재작성보다 부분 수정으로 유지한다.
- 카드 구현은 한 번에 하나씩, 공용 필드 우선으로 넣는다.
- 새 기능은 `데이터 1곳 + 런타임 1곳 + 테스트 1곳` 순서로 맞춘다.
## 읽기 원칙
- 파일은 필요한 것만 읽는다.
- 비슷한 파일은 병렬로 한 번에 확인한다.
- 같은 정보를 여러 번 요약하지 않는다.
## 쓰기 원칙
- 공용으로 표현 가능한 효과는 카드 전용 분기로 만들지 않는다.
- 같은 의미의 효과는 같은 필드 이름을 쓴다.
- 문서는 카드별 상태표와 공용 필드 사전을 분리해서 유지한다.
- 카드 `kind`는 효과와 맞춘다 — 데미지 카드=`Attack`, block·유틸만 있으면=`Skill`, 지속효과=`Power`(`powerEffect` 또는 power 필드 필수). 안 맞으면 사용 불가/死카드가 된다(Power 분기는 damage/aoe 무시, Attack은 몬스터 드롭 라우팅).
- 새 효과 필드는 Lua(`cb/*.mjs`)와 JS 미러(`tools/balance/sim-balance.mjs`) 양쪽에 구현한다(한쪽만 = 게임↔시뮬 드리프트).
## 응답 원칙
- 중간 보고는 짧게 한다.
- 바뀐 점과 남은 점만 말한다.
- 불필요한 재설명은 줄인다.
## 검증·통합 원칙
- 카드/cb 변경 후 검증 스위트를 돌린다: `node tools/verify/cardkinds.mjs`(kind↔효과)·`cbprops.mjs`(미선언 `self.X` 필드)·`cbgap.mjs`(UI 경로) + `node --test tools/balance/sim-balance.test.mjs`(이중구현 미러). 이상 0을 확인한 뒤 산출물을 갱신한다.
- 작업 브랜치에 `main`을 머지했다가 충돌·문제가 나도 그 머지 커밋을 통째로 `git revert`하지 않는다 — main에 먼저 들어간 타인 작업이 collateral로 사라진다(2026-06-30 `#98/#99``#96` 11개 수정을 이렇게 날린 사고). 소스 충돌만 해소하고 산출물(codeblock 등)은 재생성한다.
- 하네스 규칙의 최종 권위는 `RULES.md`(§1 산출물 읽기/수정 금지·§4 git/PR·§6 이중구현 동기화·§9 카드 kind)이고, codex 전용 하드룰은 `docs/codex-working-rules.md`다. 작업 전 둘 다 따른다.

View File

@@ -1,11 +0,0 @@
# Codex Working Rules
1. 사용자가 특정 클래스만 수정하라고 했으면 그 클래스 외의 데이터, 설명문, 밸런스 문구는 건드리지 않는다.
2. 기존 한글 텍스트는 요청이 없으면 임의로 바꾸지 않는다.
3. 전직 구조를 바꿀 때는 실제 직업명만 사용한다. 임의의 내부 분류명이나 새 직업명을 사용자-facing 구조에 추가하지 않는다.
4. 대량 치환 전에 수정 대상 파일과 범위를 먼저 확인하고, 원본 문자열이 깨진 상태면 치환 작업을 진행하지 않는다.
5. 생성기 파일을 크게 수정할 때는 `node --check`와 생성기 실행으로 문법을 먼저 검증한 뒤 산출물을 갱신한다.
6. 작업 브랜치에 `main`을 머지했다가 충돌·문제가 나도 **그 머지 커밋을 통째로 `git revert`하지 않는다** — main에 먼저 들어간 타인 작업이 collateral로 사라진다(2026-06-30 `#98/#99``#96`의 버그수정 11개를 이렇게 전부 날림). 소스 충돌만 해소하고 산출물(codeblock 등)은 재생성한다. (RULES §4)
7. 카드 `kind`는 효과와 일치시킨다 — 데미지 카드=`Attack`, block·유틸만 있으면=`Skill`, 지속효과=`Power`(`powerEffect` 또는 power 필드 필수). 안 맞으면 사용 불가/死카드가 된다(2026-06-30 아이언 바디=Attack인데 block만, 분노=Power인데 damage만 → 둘 다 먹통). 카드 추가/수정 후 `node tools/verify/cardkinds.mjs`로 검증(이상 0 = exit 0). (RULES §9)
8. 카드/cb 변경 후 검증 스위트를 돌린다: `node tools/verify/cardkinds.mjs`(kind↔효과)·`cbprops.mjs`(미선언 `self.X` 필드)·`cbgap.mjs`(UI 경로) + `node --test tools/balance/sim-balance.test.mjs`(이중구현 미러). 새 효과 필드는 Lua(`cb/*.mjs`)와 JS 미러(`tools/balance/sim-balance.mjs`) **양쪽**에 구현(한쪽만 = 게임↔시뮬 드리프트). (RULES §6)
9. 하네스 규칙의 권위는 `RULES.md`다 — 작업 전 RULES.md(§1 산출물 읽기/수정 금지·§4 git/PR·§6 이중구현 동기화·§9 카드 kind)를 읽고 따른다.

View File

@@ -1,8 +0,0 @@
# 전투 드로우 누적
`damagePerCardDrawnThisCombat`은 이번 전투 동안 실제로 뽑힌 카드 수를 기준으로 공격력을 올리는 공용 필드입니다.
적용 예시:
- `Murder`: 이번 전투 동안 뽑은 카드 1장당 피해량이 1 증가

View File

@@ -1,22 +0,0 @@
# 드로우 연동 효과
드로우 결과를 받아 후속 효과를 처리하는 공용 패턴을 정리합니다.
## 현재 구현
- `draw`: 카드를 뽑음
- `drawUntilHandSize`: 손패가 지정 수치가 될 때까지 뽑음
- `drawSkillBlock`: 이번 카드로 뽑힌 카드 중 스킬 카드마다 방어도를 얻음
## 동작 방식
- 드로우 함수는 이번에 뽑힌 카드 ID 목록을 반환합니다.
- 카드 효과는 그 목록을 보고 조건을 판정합니다.
- 그래서 `EscapePlan` 같은 카드뿐 아니라, 나중에 같은 규칙이 필요한 카드에도 같은 필드를 붙이면 됩니다.
## 예시
- `EscapePlan`
- `draw = 1`
- `drawSkillBlock = 3`

View File

@@ -1,5 +0,0 @@
# 불가침
`intangible`는 카드를 사용할 때 플레이어에게 불가침 수치를 부여하는 공용 필드입니다. 불가침이 남아 있는 동안 받는 피해는 1로 줄어들고, 턴이 끝날 때 1씩 감소합니다.
`endTurnDexLoss`는 그 카드가 활성화된 동안 매 턴 종료 시 민첩을 잃게 만드는 공용 필드입니다. `WraithForm` 같은 카드가 이 조합을 사용합니다.

View File

@@ -1,12 +0,0 @@
# Next Skill Repeat
`nextSkillRepeatCount`는 다음에 사용하는 스킬 카드의 효과를 추가 횟수만큼 다시 적용하는 공용 필드입니다.
현재 구현은 카드가 발동할 때 이 수치를 전역 상태에 누적해 두고, 다음 스킬 카드가 실제로 사용되면 그 효과를 같은 카드에 대해 다시 한 번 이상 적용합니다. 카드 종류는 고정하지 않았기 때문에, 같은 필드를 다른 카드에도 그대로 붙일 수 있습니다.
예시:
- `Burst`
- `nextSkillRepeatCount = 1`
- 다음 스킬을 한 번 더 적용

View File

@@ -1,5 +0,0 @@
# 처치 보상
`rewardOnKill`은 해당 카드가 적을 처치했을 때 전투 보상 화면을 한 번 더 이어서 보여주는 공용 필드입니다. 현재 보상 UI는 3장 선택을 유지하고, 보상 화면만 추가로 한 번 더 열립니다.
`TheHunt`는 이 규칙을 사용합니다. 같은 패턴이 필요한 다른 카드에도 그대로 붙일 수 있습니다.

View File

@@ -1,97 +0,0 @@
# 도적 비스킬 카드 이름
메이플스토리 원본 스킬을 바탕으로 만든 카드 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`: 칼날 부채 -> 사방 표창비

View File

@@ -1,14 +0,0 @@
# X 코스트 카드
`useAllEnergy`는 카드가 사용될 때 남은 에너지를 전부 쓰는 공용 필드입니다.
연동 필드:
- `xDamagePerEnergy`: 에너지 1당 피해량
- `xWeakPerEnergy`: 에너지 1당 약화량
적용 예시:
- `Skewer`: 남은 에너지 전부를 써서 `8 * energy` 피해
- `Malaise`: 남은 에너지 전부를 써서 약화 부여

View File

@@ -1,7 +0,0 @@
@echo off
setlocal
chcp 65001 >nul
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0tools\cards\cards_excel.ps1" import
echo.
echo Press any key to close this window.
pause >nul

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

View File

@@ -6365,11 +6365,11 @@
},
{
"id": "00000dac-0000-4000-8000-000000000dac",
"path": "/maps/map03/octopus",
"path": "/maps/map03/combat_1",
"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": "octopus",
"path": "/maps/map03/octopus",
"name": "combat_1",
"path": "/maps/map03/combat_1",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6379,12 +6379,12 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-octopus",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00000dac-0000-4000-8000-000000000dac",
"replaced_model_id": null
},
"modelId": "monster-octopus",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
@@ -6409,9 +6409,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "d8f014043ce8418f96700c2b6c9ebf6c",
"hit": "c3cf643b618346c7bfa6574187b396f9",
"die": "a88d9b3d60f941e4890dc89a6ccaa8ee"
"stand": "ed3908e24d694bb786023fc1ed073489",
"hit": "4763c9bebc9245998c9c499b6316aa9f",
"die": "b168793b92a844a3a3a6f4ce647a14d2"
},
"Enable": true
},
@@ -6421,7 +6421,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "d8f014043ce8418f96700c2b6c9ebf6c",
"SpriteRUID": "ed3908e24d694bb786023fc1ed073489",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6508,7 +6508,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "octopus",
"EnemyId": "pig",
"Group": "combat"
}
],
@@ -6517,11 +6517,11 @@
},
{
"id": "00000dad-0000-4000-8000-000000000dad",
"path": "/maps/map03/junior_neki",
"path": "/maps/map03/combat_2",
"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": "junior_neki",
"path": "/maps/map03/junior_neki",
"name": "combat_2",
"path": "/maps/map03/combat_2",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6531,169 +6531,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-junior_neki",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00000dad-0000-4000-8000-000000000dad",
"replaced_model_id": null
},
"modelId": "monster-junior_neki",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 3.7333333333333334,
"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": "48c10437ae8344a9b2a1d3f36185728f",
"hit": "9044063647854f5e9128efcf80e909be",
"die": "f414577d18c94cc387c275df4abdbc3b"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "48c10437ae8344a9b2a1d3f36185728f",
"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": 0.63,
"y": 0.58
},
"ColliderOffset": {
"x": 0.0449999869,
"y": 0.29
},
"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,
"EnemyId": "junior_neki",
"Group": "combat"
}
],
"@version": 1
}
},
{
"id": "00000dae-0000-4000-8000-000000000dae",
"path": "/maps/map03/junior_bugi",
"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": "junior_bugi",
"path": "/maps/map03/junior_bugi",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 4,
"pathConstraints": "///",
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-junior_bugi",
"sub_entity_id": null,
"root_entity_id": "00000dae-0000-4000-8000-000000000dae",
"replaced_model_id": null
},
"modelId": "monster-junior_bugi",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 5.166666666666666,
"x": 3.8,
"y": 0.03499998,
"z": 999.999
},
@@ -6812,7 +6660,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "junior_bugi",
"EnemyId": "red_snail",
"Group": "combat"
}
],
@@ -6820,12 +6668,12 @@
}
},
{
"id": "00000daf-0000-4000-8000-000000000daf",
"path": "/maps/map03/slime",
"id": "00000dae-0000-4000-8000-000000000dae",
"path": "/maps/map03/combat_3",
"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": "slime",
"path": "/maps/map03/slime",
"name": "combat_3",
"path": "/maps/map03/combat_3",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6835,17 +6683,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-slime",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00000daf-0000-4000-8000-000000000daf",
"root_entity_id": "00000dae-0000-4000-8000-000000000dae",
"replaced_model_id": null
},
"modelId": "monster-slime",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 6.6,
"x": 5.2,
"y": 0.03499998,
"z": 999.999
},
@@ -6865,11 +6713,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
"stand": "48c10437ae8344a9b2a1d3f36185728f",
"hit": "9044063647854f5e9128efcf80e909be",
"die": "f414577d18c94cc387c275df4abdbc3b"
},
"Enable": true
},
@@ -6879,7 +6725,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "50faf654ee5d479cb2958edce9feaef0",
"SpriteRUID": "48c10437ae8344a9b2a1d3f36185728f",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6966,7 +6812,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "slime",
"EnemyId": "orange_mushroom",
"Group": "combat"
}
],
@@ -6974,12 +6820,12 @@
}
},
{
"id": "00000db0-0000-4000-8000-000000000db0",
"path": "/maps/map03/slime_elite",
"id": "00000daf-0000-4000-8000-000000000daf",
"path": "/maps/map03/elite_4",
"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": "slime_elite",
"path": "/maps/map03/slime_elite",
"name": "elite_4",
"path": "/maps/map03/elite_4",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6989,17 +6835,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-slime_elite",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00000db0-0000-4000-8000-000000000db0",
"root_entity_id": "00000daf-0000-4000-8000-000000000daf",
"replaced_model_id": null
},
"modelId": "monster-slime_elite",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4.3,
"x": 3,
"y": 0.03499998,
"z": 999.999
},
@@ -7019,11 +6865,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "50faf654ee5d479cb2958edce9feaef0",
"move": "dc932872543f4a02bf41e977ab79e5ad",
"hit": "61c27025a8f14c478f30ede1b49758bc",
"jump": "8b89d86b1a9c4c4288650614c6f30e67",
"die": "31ecb6c7cbc24599881f00cb01599f09"
"stand": "d8f014043ce8418f96700c2b6c9ebf6c",
"hit": "c3cf643b618346c7bfa6574187b396f9",
"die": "a88d9b3d60f941e4890dc89a6ccaa8ee"
},
"Enable": true
},
@@ -7033,7 +6877,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "50faf654ee5d479cb2958edce9feaef0",
"SpriteRUID": "d8f014043ce8418f96700c2b6c9ebf6c",
"StartFrameIndex": 0,
"Enable": true
},
@@ -7063,8 +6907,8 @@
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 0.85,
"y": 0.78
"x": 0.63,
"y": 0.58
},
"ColliderOffset": {
"x": 0.0449999869,
@@ -7120,7 +6964,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "slime_elite",
"EnemyId": "modified_snail",
"Group": "elite"
}
],
@@ -7128,12 +6972,12 @@
}
},
{
"id": "00000db1-0000-4000-8000-000000000db1",
"path": "/maps/map03/king_slime",
"id": "00000db0-0000-4000-8000-000000000db0",
"path": "/maps/map03/elite_5",
"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": "king_slime",
"path": "/maps/map03/king_slime",
"name": "elite_5",
"path": "/maps/map03/elite_5",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -7143,17 +6987,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-king_slime",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00000db1-0000-4000-8000-000000000db1",
"root_entity_id": "00000db0-0000-4000-8000-000000000db0",
"replaced_model_id": null
},
"modelId": "monster-king_slime",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4.6,
"x": 5,
"y": 0.03499998,
"z": 999.999
},
@@ -7173,13 +7017,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"move": "873425127b75475b9944dc86bf77f885",
"stand": "dd9de73d580240faab8cad03b587013b",
"jump": "6a2b983b7a31417ca19c29c3d1d00817",
"attack": "a34d1146057443fd8b578dafeb7c2ed1",
"skill": "0b0bb78f0ca44526bad6d994bb16f973",
"hit": "d2de42d3233b42a58d9799d5e762a19c",
"die": "5bd3969c3bcb4df2bd79c2b940ee03dc"
"stand": "4ca39dbfa1c6492283ba8bd352d12b0a",
"hit": "7ac78511036e4ebe988b97c35fc275d1",
"die": "740f3f2b2e7a4b71bec5eac84e8539f9"
},
"Enable": true
},
@@ -7189,7 +7029,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "dd9de73d580240faab8cad03b587013b",
"SpriteRUID": "4ca39dbfa1c6492283ba8bd352d12b0a",
"StartFrameIndex": 0,
"Enable": true
},
@@ -7219,12 +7059,12 @@
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 2.19,
"y": 1.39
"x": 0.63,
"y": 0.58
},
"ColliderOffset": {
"x": 0.335000038,
"y": 0.695
"x": 0.0449999869,
"y": 0.29
},
"IsLegacy": false,
"Enable": true
@@ -7276,7 +7116,159 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "king_slime",
"EnemyId": "mushmom",
"Group": "elite"
}
],
"@version": 1
}
},
{
"id": "00000db1-0000-4000-8000-000000000db1",
"path": "/maps/map03/boss_6",
"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/map03/boss_6",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 4,
"pathConstraints": "///",
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00000db1-0000-4000-8000-000000000db1",
"replaced_model_id": null
},
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4,
"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": "f86992ba9c41487c8480fcb893fcbda6",
"hit": "d305b942b1704c8084548108ff3b7a6b",
"die": "5a563e5fd98c4132b61057dc6bb8aaf2"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "f86992ba9c41487c8480fcb893fcbda6",
"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": 0.63,
"y": 0.58
},
"ColliderOffset": {
"x": 0.0449999869,
"y": 0.29
},
"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,
"EnemyId": "slime_boss",
"Group": "boss"
}
],

View File

@@ -6365,11 +6365,11 @@
},
{
"id": "00001194-0000-4000-8000-000000001194",
"path": "/maps/map04/kapa_drake",
"path": "/maps/map04/combat_1",
"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": "kapa_drake",
"path": "/maps/map04/kapa_drake",
"name": "combat_1",
"path": "/maps/map04/combat_1",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6379,12 +6379,12 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-kapa_drake",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00001194-0000-4000-8000-000000001194",
"replaced_model_id": null
},
"modelId": "monster-kapa_drake",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
@@ -6409,9 +6409,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "4ca39dbfa1c6492283ba8bd352d12b0a",
"hit": "7ac78511036e4ebe988b97c35fc275d1",
"die": "740f3f2b2e7a4b71bec5eac84e8539f9"
"stand": "f86992ba9c41487c8480fcb893fcbda6",
"hit": "d305b942b1704c8084548108ff3b7a6b",
"die": "5a563e5fd98c4132b61057dc6bb8aaf2"
},
"Enable": true
},
@@ -6421,7 +6421,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "4ca39dbfa1c6492283ba8bd352d12b0a",
"SpriteRUID": "f86992ba9c41487c8480fcb893fcbda6",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6508,7 +6508,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "kapa_drake",
"EnemyId": "blue_mushroom",
"Group": "combat"
}
],
@@ -6517,11 +6517,11 @@
},
{
"id": "00001195-0000-4000-8000-000000001195",
"path": "/maps/map04/junior_neki",
"path": "/maps/map04/combat_2",
"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": "junior_neki",
"path": "/maps/map04/junior_neki",
"name": "combat_2",
"path": "/maps/map04/combat_2",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6531,17 +6531,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-junior_neki",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00001195-0000-4000-8000-000000001195",
"replaced_model_id": null
},
"modelId": "monster-junior_neki",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 3.7333333333333334,
"x": 3.8,
"y": 0.03499998,
"z": 999.999
},
@@ -6561,9 +6561,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "48c10437ae8344a9b2a1d3f36185728f",
"hit": "9044063647854f5e9128efcf80e909be",
"die": "f414577d18c94cc387c275df4abdbc3b"
"stand": "d8f014043ce8418f96700c2b6c9ebf6c",
"hit": "c3cf643b618346c7bfa6574187b396f9",
"die": "a88d9b3d60f941e4890dc89a6ccaa8ee"
},
"Enable": true
},
@@ -6573,7 +6573,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "48c10437ae8344a9b2a1d3f36185728f",
"SpriteRUID": "d8f014043ce8418f96700c2b6c9ebf6c",
"StartFrameIndex": 0,
"Enable": true
},
@@ -6660,7 +6660,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "junior_neki",
"EnemyId": "stump",
"Group": "combat"
}
],
@@ -6669,11 +6669,11 @@
},
{
"id": "00001196-0000-4000-8000-000000001196",
"path": "/maps/map04/junior_bugi",
"path": "/maps/map04/combat_3",
"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": "junior_bugi",
"path": "/maps/map04/junior_bugi",
"name": "combat_3",
"path": "/maps/map04/combat_3",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6683,17 +6683,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-junior_bugi",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00001196-0000-4000-8000-000000001196",
"replaced_model_id": null
},
"modelId": "monster-junior_bugi",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 5.166666666666666,
"x": 5.2,
"y": 0.03499998,
"z": 999.999
},
@@ -6812,7 +6812,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "junior_bugi",
"EnemyId": "green_mushroom",
"Group": "combat"
}
],
@@ -6821,11 +6821,11 @@
},
{
"id": "00001197-0000-4000-8000-000000001197",
"path": "/maps/map04/stump",
"path": "/maps/map04/elite_4",
"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": "stump",
"path": "/maps/map04/stump",
"name": "elite_4",
"path": "/maps/map04/elite_4",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -6835,17 +6835,169 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-stump",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00001197-0000-4000-8000-000000001197",
"replaced_model_id": null
},
"modelId": "monster-stump",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 6.6,
"x": 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": "17b55730c26f4fd6b8fcfa288da388de",
"hit": "eac48e84a9fc4580a4018de5cf52ddb3",
"die": "51c2f4b59a2c413db26035aa57002fc8"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "17b55730c26f4fd6b8fcfa288da388de",
"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": 0.63,
"y": 0.58
},
"ColliderOffset": {
"x": 0.0449999869,
"y": 0.29
},
"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,
"EnemyId": "modified_snail",
"Group": "elite"
}
],
"@version": 1
}
},
{
"id": "00001198-0000-4000-8000-000000001198",
"path": "/maps/map04/elite_5",
"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/map04/elite_5",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 4,
"pathConstraints": "///",
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00001198-0000-4000-8000-000000001198",
"replaced_model_id": null
},
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 5,
"y": 0.03499998,
"z": 999.999
},
@@ -6866,7 +7018,6 @@
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "ed3908e24d694bb786023fc1ed073489",
"move": "9a4cad470f304753885e06c043156efb",
"hit": "4763c9bebc9245998c9c499b6316aa9f",
"die": "b168793b92a844a3a3a6f4ce647a14d2"
},
@@ -6965,161 +7116,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "stump",
"Group": "combat"
}
],
"@version": 1
}
},
{
"id": "00001198-0000-4000-8000-000000001198",
"path": "/maps/map04/dile",
"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": "dile",
"path": "/maps/map04/dile",
"nameEditable": true,
"enable": true,
"visible": true,
"localize": false,
"displayOrder": 4,
"pathConstraints": "///",
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-dile",
"sub_entity_id": null,
"root_entity_id": "00001198-0000-4000-8000-000000001198",
"replaced_model_id": null
},
"modelId": "monster-dile",
"@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": {
"move": "426ba2c6fa2d4cdd92bcb0bb37861dcc",
"stand": "68070c6f4abe40658899a208ddaf4081",
"skill": "4ba2cdc2f11746afa0f542293b0618d5",
"hit": "172640e6d4ce444aa1dfbd9bd9523eb1",
"die": "5d50d9aa34c745b9b8932c15da919927"
},
"Enable": true
},
{
"@type": "MOD.Core.SpriteRendererComponent",
"ActionSheet": {},
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "68070c6f4abe40658899a208ddaf4081",
"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": 2.2,
"y": 1.51
},
"ColliderOffset": {
"x": -0.220000029,
"y": 0.755
},
"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,
"EnemyId": "dile",
"EnemyId": "mushmom",
"Group": "elite"
}
],
@@ -7128,11 +7125,11 @@
},
{
"id": "00001199-0000-4000-8000-000000001199",
"path": "/maps/map04/mushmom",
"path": "/maps/map04/boss_6",
"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/map04/mushmom",
"name": "boss_6",
"path": "/maps/map04/boss_6",
"nameEditable": true,
"enable": true,
"visible": true,
@@ -7142,17 +7139,17 @@
"revision": 2,
"origin": {
"type": "Model",
"entry_id": "monster-mushmom",
"entry_id": "ChaseMonster",
"sub_entity_id": null,
"root_entity_id": "00001199-0000-4000-8000-000000001199",
"replaced_model_id": null
},
"modelId": "monster-mushmom",
"modelId": "chasemonster",
"@components": [
{
"@type": "MOD.Core.TransformComponent",
"Position": {
"x": 4.6,
"x": 4,
"y": 0.03499998,
"z": 999.999
},
@@ -7172,12 +7169,9 @@
{
"@type": "MOD.Core.StateAnimationComponent",
"ActionSheet": {
"stand": "23c38ef3acad4a30ad59120bb939b008",
"move": "24d8a3a75f96406ba690ed42d7250b8f",
"hit": "c826e36ee89c48bca6aab856aa773f38",
"attack": "4d7465e950144dc59c263aad01b14e14",
"jump": "b7ddbda71a294141ba134249fc34c7da",
"die": "f50664a4524147399359cb90a6f3e80c"
"stand": "48c10437ae8344a9b2a1d3f36185728f",
"hit": "9044063647854f5e9128efcf80e909be",
"die": "f414577d18c94cc387c275df4abdbc3b"
},
"Enable": true
},
@@ -7187,7 +7181,7 @@
"EndFrameIndex": 0,
"RenderSetting": 1,
"SortingLayer": "MapLayer0",
"SpriteRUID": "23c38ef3acad4a30ad59120bb939b008",
"SpriteRUID": "48c10437ae8344a9b2a1d3f36185728f",
"StartFrameIndex": 0,
"Enable": true
},
@@ -7217,12 +7211,12 @@
{
"@type": "MOD.Core.HitComponent",
"BoxSize": {
"x": 1.2,
"y": 1.1
"x": 0.63,
"y": 0.58
},
"ColliderOffset": {
"x": 0.02,
"y": 0.55
"x": 0.0449999869,
"y": 0.29
},
"IsLegacy": false,
"Enable": true
@@ -7274,7 +7268,7 @@
{
"@type": "script.CombatMonster",
"Enable": true,
"EnemyId": "mushmom",
"EnemyId": "slime_boss",
"Group": "boss"
}
],

File diff suppressed because it is too large Load Diff

View File

@@ -1,275 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,334 +0,0 @@
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

@@ -1,38 +0,0 @@
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);
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,629 +0,0 @@
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateSet('export', 'import')]
[string]$Action,
[string]$JsonPath,
[string]$XlsxPath,
[string]$OutJsonPath
)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.IO.Compression
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if ([string]::IsNullOrWhiteSpace($JsonPath)) { $JsonPath = Join-Path $repoRoot 'data\cards.json' }
if ([string]::IsNullOrWhiteSpace($XlsxPath)) { $XlsxPath = Join-Path $repoRoot 'data\cards.xlsx' }
if ([string]::IsNullOrWhiteSpace($OutJsonPath)) { $OutJsonPath = $JsonPath }
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
function Escape-Xml([string]$Text) {
if ($null -eq $Text) { return '' }
return [System.Security.SecurityElement]::Escape($Text)
}
function Get-ColumnName([int]$Index) {
$n = $Index
$name = ''
while ($n -gt 0) {
$n--
$name = [char][int](65 + ($n % 26)) + $name
$n = [math]::Floor($n / 26)
}
return $name
}
function Get-ColumnIndex([string]$Name) {
$n = 0
foreach ($ch in $Name.ToCharArray()) {
if ($ch -match '[A-Z]') {
$n = $n * 26 + ([int][char]$ch - 64)
}
}
return $n
}
function Get-CellRef([int]$Col, [int]$Row) {
return (Get-ColumnName $Col) + $Row
}
function Has-MapKey($Map, $Key) {
if ($null -eq $Map) { return $false }
if ($null -eq $Key) { return $false }
if ($Key -is [string] -and [string]::IsNullOrWhiteSpace($Key)) { return $false }
foreach ($existingKey in $Map.Keys) {
if ($existingKey -eq $Key) { return $true }
}
return $false
}
function Get-ScalarType($Value) {
if ($null -eq $Value) { return 'null' }
if ($Value -is [bool]) { return 'boolean' }
if ($Value -is [byte] -or $Value -is [sbyte] -or
$Value -is [int16] -or $Value -is [uint16] -or
$Value -is [int32] -or $Value -is [uint32] -or
$Value -is [int64] -or $Value -is [uint64] -or
$Value -is [single] -or $Value -is [double] -or $Value -is [decimal]) { return 'number' }
if ($Value -is [string]) { return 'string' }
return 'string'
}
function Get-CardSchema($Cards) {
$schema = [ordered]@{}
foreach ($cardEntry in $Cards.PSObject.Properties) {
$card = $cardEntry.Value
foreach ($prop in $card.PSObject.Properties) {
$kind = Get-ScalarType $prop.Value
if (-not (Has-MapKey $schema $prop.Name)) {
$schema[$prop.Name] = $kind
} elseif ($schema[$prop.Name] -ne $kind -and $kind -ne 'null') {
$schema[$prop.Name] = 'string'
}
}
}
return $schema
}
function Get-ColumnWidth([string]$Header, [string]$Type) {
switch ($Header) {
'id' { return 18 }
'name' { return 24 }
'desc' { return 48 }
'image' { return 36 }
'fx' { return 36 }
'kind' { return 12 }
'class' { return 12 }
'rarity' { return 12 }
default {
if ($Type -eq 'boolean') { return 10 }
if ($Type -eq 'number') { return 12 }
return 16
}
}
}
function To-InvariantNumber($Value) {
return [string]::Format([System.Globalization.CultureInfo]::InvariantCulture, '{0}', $Value)
}
function New-HeaderCellXml([string]$Ref, [string]$Text) {
$escaped = Escape-Xml $Text
return "<c r=""$Ref"" s=""1"" t=""inlineStr""><is><t xml:space=""preserve"">$escaped</t></is></c>"
}
function New-TextCellXml([string]$Ref, [string]$Text) {
$escaped = Escape-Xml $Text
return "<c r=""$Ref"" t=""inlineStr""><is><t xml:space=""preserve"">$escaped</t></is></c>"
}
function New-NumberCellXml([string]$Ref, $Value) {
if ($null -eq $Value) { return $null }
if ($Value -is [string] -and $Value -eq '') { return $null }
return "<c r=""$Ref""><v>$(To-InvariantNumber $Value)</v></c>"
}
function New-BoolCellXml([string]$Ref, $Value) {
if ($null -eq $Value) { return $null }
if ($Value -is [string] -and $Value -eq '') { return $null }
$bool = $false
if ($Value -is [bool]) {
$bool = $Value
} else {
$text = [string]$Value
if ($text -match '^(?i:true|1|yes|y)$') { $bool = $true }
elseif ($text -match '^(?i:false|0|no|n)$') { $bool = $false }
else { return $null }
}
$n = if ($bool) { 1 } else { 0 }
return "<c r=""$Ref"" t=""b""><v>$n</v></c>"
}
function New-CellXml([string]$Ref, $Value, [string]$Type) {
switch ($Type) {
'number' { return New-NumberCellXml $Ref $Value }
'boolean' { return New-BoolCellXml $Ref $Value }
default {
if ($null -eq $Value) {
return New-TextCellXml $Ref ''
}
return New-TextCellXml $Ref ([string]$Value)
}
}
}
function Get-WorksheetXml([string]$SheetName, [string[]]$Headers, [object[]]$Rows, [hashtable]$TypeMap) {
$maxCol = $Headers.Count
$lastCol = Get-ColumnName $maxCol
$rowCount = $Rows.Count + 1
$colsXml = New-Object System.Collections.Generic.List[string]
for ($i = 0; $i -lt $Headers.Count; $i++) {
$header = $Headers[$i]
$type = if (Has-MapKey $TypeMap $header) { [string]$TypeMap[$header] } else { 'string' }
$width = Get-ColumnWidth $header $type
$colsXml.Add("<col min=""$($i + 1)"" max=""$($i + 1)"" width=""$width"" customWidth=""1"" />")
}
$rowsXml = New-Object System.Collections.Generic.List[string]
$headerCells = New-Object System.Collections.Generic.List[string]
for ($i = 0; $i -lt $Headers.Count; $i++) {
$headerCells.Add((New-HeaderCellXml (Get-CellRef ($i + 1) 1) $Headers[$i]))
}
$rowsXml.Add("<row r=""1"" spans=""1:$maxCol"" ht=""20"" customHeight=""1"">$($headerCells -join '')</row>")
for ($r = 0; $r -lt $Rows.Count; $r++) {
$row = $Rows[$r]
$cells = New-Object System.Collections.Generic.List[string]
for ($c = 0; $c -lt $Headers.Count; $c++) {
$header = $Headers[$c]
$type = if (Has-MapKey $TypeMap $header) { [string]$TypeMap[$header] } else { 'string' }
$value = $null
if (Has-MapKey $row $header) { $value = $row[$header] }
$cellXml = New-CellXml (Get-CellRef ($c + 1) ($r + 2)) $value $type
if ($null -ne $cellXml) { $cells.Add($cellXml) }
}
$rowsXml.Add("<row r=""$($r + 2)"" spans=""1:$maxCol"">$($cells -join '')</row>")
}
$sheetView = '<sheetViews><sheetView workbookViewId="0"><pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/><selection pane="bottomLeft" activeCell="A2" sqref="A2"/></sheetView></sheetViews>'
$cols = '<cols>' + ($colsXml -join '') + '</cols>'
$sheetData = '<sheetData>' + ($rowsXml -join '') + '</sheetData>'
$autoFilter = "<autoFilter ref=""A1:$lastCol$rowCount""/>"
return @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
$sheetView
<sheetFormatPr defaultRowHeight="18"/>
$cols
$sheetData
$autoFilter
<pageMargins left="0.25" right="0.25" top="0.5" bottom="0.5" header="0.3" footer="0.3"/>
</worksheet>
"@
}
function Get-StylesXml {
return @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<fonts count="2">
<font>
<sz val="11"/>
<color rgb="FF000000"/>
<name val="Calibri"/>
<family val="2"/>
<scheme val="minor"/>
</font>
<font>
<b/>
<sz val="11"/>
<color rgb="FFFFFFFF"/>
<name val="Calibri"/>
<family val="2"/>
<scheme val="minor"/>
</font>
</fonts>
<fills count="2">
<fill><patternFill patternType="none"/></fill>
<fill><patternFill patternType="solid"><fgColor rgb="FF2D3748"/><bgColor indexed="64"/></patternFill></fill>
</fills>
<borders count="1">
<border>
<left/><right/><top/><bottom/><diagonal/>
</border>
</borders>
<cellStyleXfs count="1">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
</cellStyleXfs>
<cellXfs count="2">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="1" fillId="1" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
</cellXfs>
<cellStyles count="1">
<cellStyle name="Normal" xfId="0" builtinId="0"/>
</cellStyles>
</styleSheet>
"@
}
function Get-WorkbookXml([string[]]$SheetNames) {
$sheetsXml = New-Object System.Collections.Generic.List[string]
for ($i = 0; $i -lt $SheetNames.Count; $i++) {
$sheetsXml.Add("<sheet name=""$(Escape-Xml $SheetNames[$i])"" sheetId=""$($i + 1)"" r:id=""rId$($i + 1)""/>")
}
return @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>
$($sheetsXml -join '')
</sheets>
</workbook>
"@
}
function Get-WorkbookRelsXml([int]$SheetCount) {
$rels = New-Object System.Collections.Generic.List[string]
for ($i = 1; $i -le $SheetCount; $i++) {
$rels.Add("<Relationship Id=""rId$i"" Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""worksheets/sheet$i.xml""/>")
}
$rels.Add("<Relationship Id=""rId$($SheetCount + 1)"" Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"" Target=""styles.xml""/>")
return @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
$($rels -join '')
</Relationships>
"@
}
function Get-RootRelsXml {
return @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>
"@
}
function Get-ContentTypesXml([int]$SheetCount) {
$overrides = New-Object System.Collections.Generic.List[string]
for ($i = 1; $i -le $SheetCount; $i++) {
$overrides.Add("<Override PartName=""/xl/worksheets/sheet$i.xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml""/>")
}
$overrides.Add('<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>')
$overrides.Add('<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>')
return @"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
$($overrides -join '')
</Types>
"@
}
function Write-Xlsx([string]$Path, [hashtable]$Parts) {
$dir = Split-Path -Parent $Path
if (-not [string]::IsNullOrWhiteSpace($dir) -and -not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
if (Test-Path $Path) {
Remove-Item -LiteralPath $Path -Force
}
$file = [System.IO.File]::Open($Path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite)
try {
$zip = New-Object System.IO.Compression.ZipArchive($file, [System.IO.Compression.ZipArchiveMode]::Create, $false)
try {
foreach ($entryName in $Parts.Keys) {
$entry = $zip.CreateEntry($entryName)
$stream = $entry.Open()
$writer = New-Object System.IO.StreamWriter($stream, $utf8NoBom)
try {
$writer.Write([string]$Parts[$entryName])
} finally {
$writer.Dispose()
$stream.Dispose()
}
}
} finally {
$zip.Dispose()
}
} finally {
$file.Dispose()
}
}
function Read-XlsxXml([string]$Path, [string]$EntryName) {
$zip = [System.IO.Compression.ZipFile]::OpenRead($Path)
try {
$entry = $zip.GetEntry($EntryName)
if ($null -eq $entry) { throw "Missing XLSX entry: $EntryName" }
$stream = $entry.Open()
try {
$reader = New-Object System.IO.StreamReader($stream, $utf8NoBom)
try { return $reader.ReadToEnd() } finally { $reader.Dispose() }
} finally {
$stream.Dispose()
}
} finally {
$zip.Dispose()
}
}
function Read-SharedStrings([string]$Path) {
try {
$xmlText = Read-XlsxXml $Path 'xl/sharedStrings.xml'
} catch {
return @()
}
[xml]$xml = $xmlText
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace('x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main')
$items = $xml.SelectNodes('/x:sst/x:si', $ns)
$values = New-Object System.Collections.Generic.List[string]
foreach ($item in $items) {
$values.Add([string]$item.InnerText)
}
return $values.ToArray()
}
function Read-WorksheetRows([string]$XmlText, [string[]]$SharedStrings) {
[xml]$xml = $XmlText
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace('x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main')
$rows = $xml.SelectNodes('/x:worksheet/x:sheetData/x:row', $ns)
$parsed = @()
foreach ($row in $rows) {
$cells = @{}
foreach ($cell in @($row.ChildNodes)) {
if ($cell.Name -ne 'c') { continue }
$ref = [string]$cell.Attributes['r'].Value
$col = Get-ColumnIndex (($ref -replace '\d+$', ''))
$type = [string]$cell.Attributes['t'].Value
$text = [string]$cell.InnerText
if ($type -eq 's' -and $text -match '^\d+$') {
$index = [int]$text
if ($index -ge 0 -and $index -lt $SharedStrings.Count) {
$text = [string]$SharedStrings[$index]
}
}
$cells[$col] = $text
}
$parsed += ,$cells
}
return $parsed
}
function Convert-CellValue([string]$Text, [string]$Type) {
if ($null -eq $Text -or $Text -eq '') { return $null }
switch ($Type) {
'number' {
$num = 0
if ([double]::TryParse($Text, [System.Globalization.NumberStyles]::Any, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$num)) {
if ([math]::Abs($num - [math]::Round($num)) -lt 0.0000001) { return [int64][math]::Round($num) }
return $num
}
return $null
}
'boolean' {
if ($Text -match '^(?i:true|1|yes|y)$') { return $true }
if ($Text -match '^(?i:false|0|no|n)$') { return $false }
return $null
}
default {
return $Text
}
}
}
function Export-Cards {
$source = Get-Content -LiteralPath $JsonPath -Raw -Encoding utf8 | ConvertFrom-Json
$schema = Get-CardSchema $source.cards
$cardCore = @('id', 'name', 'cost', 'kind', 'rarity', 'class', 'desc', 'image', 'fx')
$cardExtras = @($schema.Keys | Where-Object { $_ -notin $cardCore } | Sort-Object)
$cardHeaders = @($cardCore + $cardExtras)
$maxDeckSize = 0
foreach ($deckEntry in $source.starterDecks.PSObject.Properties) {
$deckSize = @($deckEntry.Value).Count
if ($deckSize -gt $maxDeckSize) {
$maxDeckSize = $deckSize
}
}
if ($maxDeckSize -lt 1) { $maxDeckSize = 1 }
$starterDeckHeaders = New-Object System.Collections.Generic.List[string]
$starterDeckHeaders.Add('class')
for ($i = 1; $i -le $maxDeckSize; $i++) {
$starterDeckHeaders.Add("slot$i")
}
$cardRows = New-Object System.Collections.Generic.List[object]
foreach ($cardEntry in $source.cards.PSObject.Properties) {
$cardId = $cardEntry.Name
$card = $cardEntry.Value
$row = [ordered]@{ id = $cardId }
foreach ($header in $cardHeaders) {
if ($header -eq 'id') { continue }
if ($card.PSObject.Properties.Name -contains $header) {
$row[$header] = $card.$header
} else {
$row[$header] = $null
}
}
$cardRows.Add($row)
}
$deckRows = New-Object System.Collections.Generic.List[object]
foreach ($deckEntry in $source.starterDecks.PSObject.Properties) {
$cls = $deckEntry.Name
$deck = @($deckEntry.Value)
$row = [ordered]@{ class = $cls }
for ($i = 1; $i -le $maxDeckSize; $i++) {
$key = "slot$i"
$row[$key] = if ($i -le $deck.Count) { $deck[$i - 1] } else { $null }
}
$deckRows.Add($row)
}
$cardSheet = Get-WorksheetXml 'Cards' $cardHeaders $cardRows $schema
$deckTypeMap = [ordered]@{ class = 'string' }
for ($i = 1; $i -le $maxDeckSize; $i++) { $deckTypeMap["slot$i"] = 'string' }
$deckSheet = Get-WorksheetXml 'StarterDecks' $starterDeckHeaders $deckRows $deckTypeMap
$parts = [ordered]@{
'[Content_Types].xml' = (Get-ContentTypesXml 2)
'_rels/.rels' = (Get-RootRelsXml)
'xl/workbook.xml' = (Get-WorkbookXml @('Cards', 'StarterDecks'))
'xl/_rels/workbook.xml.rels' = (Get-WorkbookRelsXml 2)
'xl/styles.xml' = (Get-StylesXml)
'xl/worksheets/sheet1.xml' = $cardSheet
'xl/worksheets/sheet2.xml' = $deckSheet
}
Write-Host "Source JSON: $JsonPath"
Write-Host "Target XLSX: $XlsxPath"
Write-Xlsx $XlsxPath $parts
Write-Host "Excel export complete: $XlsxPath"
}
function Import-Cards {
$source = Get-Content -LiteralPath $JsonPath -Raw -Encoding utf8 | ConvertFrom-Json
$schema = Get-CardSchema $source.cards
$origCardOrders = @{}
foreach ($cardEntry in $source.cards.PSObject.Properties) {
$origCardOrders[$cardEntry.Name] = @($cardEntry.Value.PSObject.Properties.Name)
}
$origDeckOrder = @($source.starterDecks.PSObject.Properties.Name)
$sharedStrings = Read-SharedStrings $XlsxPath
$cardsXml = Read-XlsxXml $XlsxPath 'xl/worksheets/sheet1.xml'
$deckXml = Read-XlsxXml $XlsxPath 'xl/worksheets/sheet2.xml'
$cardRowsRaw = Read-WorksheetRows $cardsXml $sharedStrings
$deckRowsRaw = Read-WorksheetRows $deckXml $sharedStrings
if ($cardRowsRaw.Count -lt 2) { throw 'Cards sheet has no data rows.' }
if ($deckRowsRaw.Count -lt 2) { throw 'StarterDecks sheet has no data rows.' }
$cardHeaderMap = $cardRowsRaw[0]
$cardHeaders = @($cardHeaderMap.Keys | Sort-Object)
$orderedCardHeaders = New-Object System.Collections.Generic.List[string]
foreach ($col in $cardHeaders) {
$header = $cardHeaderMap[$col]
if ([string]::IsNullOrWhiteSpace($header)) { continue }
$orderedCardHeaders.Add($header)
}
$newCards = [ordered]@{}
for ($r = 1; $r -lt $cardRowsRaw.Count; $r++) {
$row = $cardRowsRaw[$r]
$cardId = $null
$rowValues = @{}
for ($c = 0; $c -lt $orderedCardHeaders.Count; $c++) {
$header = $orderedCardHeaders[$c]
$text = $null
if (Has-MapKey $row ($c + 1)) { $text = $row[$c + 1] }
if ($header -eq 'id') {
$cardId = [string]$text
continue
}
$rowValues[$header] = $text
}
if (-not [string]::IsNullOrWhiteSpace($cardId)) {
$cardObj = [ordered]@{}
$fieldOrder = New-Object System.Collections.Generic.List[string]
if ($origCardOrders.ContainsKey($cardId)) {
foreach ($name in @($origCardOrders[$cardId])) {
if ($name -ne 'id' -and -not $fieldOrder.Contains($name)) {
$fieldOrder.Add($name)
}
}
}
foreach ($name in $orderedCardHeaders) {
if ($name -ne 'id' -and -not $fieldOrder.Contains($name)) {
$fieldOrder.Add($name)
}
}
foreach ($header in $fieldOrder) {
$text = $null
if (Has-MapKey $rowValues $header) { $text = $rowValues[$header] }
$type = if (Has-MapKey $schema $header) { [string]$schema[$header] } else { 'string' }
$value = Convert-CellValue $text $type
if ($null -eq $value) { continue }
$cardObj[$header] = $value
}
$newCards[$cardId] = $cardObj
}
}
$deckHeaderMap = $deckRowsRaw[0]
$deckHeaderCols = @($deckHeaderMap.Keys | Sort-Object)
$orderedDeckHeaders = New-Object System.Collections.Generic.List[string]
foreach ($col in $deckHeaderCols) {
$header = $deckHeaderMap[$col]
if ([string]::IsNullOrWhiteSpace($header)) { continue }
$orderedDeckHeaders.Add($header)
}
$newDecks = [ordered]@{}
for ($r = 1; $r -lt $deckRowsRaw.Count; $r++) {
$row = $deckRowsRaw[$r]
$cls = $null
$deckValues = @{}
for ($c = 0; $c -lt $orderedDeckHeaders.Count; $c++) {
$header = $orderedDeckHeaders[$c]
$text = $null
if (Has-MapKey $row ($c + 1)) { $text = $row[$c + 1] }
if ($header -eq 'class') {
$cls = [string]$text
continue
}
$deckValues[$header] = $text
}
if (-not [string]::IsNullOrWhiteSpace($cls)) {
$deck = New-Object System.Collections.Generic.List[string]
foreach ($header in $orderedDeckHeaders) {
if ($header -eq 'class') { continue }
$text = $null
if (Has-MapKey $deckValues $header) { $text = $deckValues[$header] }
if (-not [string]::IsNullOrWhiteSpace([string]$text)) {
$deck.Add([string]$text)
}
}
$newDecks[$cls] = $deck.ToArray()
}
}
if ($origDeckOrder.Count -gt 0) {
$orderedDecks = [ordered]@{}
foreach ($cls in $origDeckOrder) {
if (Has-MapKey $newDecks $cls) {
$orderedDecks[$cls] = $newDecks[$cls]
}
}
foreach ($entry in $newDecks.GetEnumerator()) {
if (-not (Has-MapKey $orderedDecks $entry.Key)) {
$orderedDecks[$entry.Key] = $entry.Value
}
}
$newDecks = $orderedDecks
}
$out = [ordered]@{
cards = $newCards
starterDecks = $newDecks
}
$json = $out | ConvertTo-Json -Depth 64
Write-Host "Source XLSX: $XlsxPath"
Write-Host "Target JSON: $OutJsonPath"
[System.IO.File]::WriteAllText($OutJsonPath, $json, $utf8NoBom)
Write-Host "JSON import complete: $OutJsonPath"
}
switch ($Action) {
'export' { Export-Cards }
'import' { Import-Cards }
}

View File

@@ -11,14 +11,14 @@ self:RenderCharacterSelect()`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' },
]),
method('RenderCharacterSelect', `local base = "/ui/SelectUIGroup/CharacterSelectHud"
local arts = { { p = "/WarriorButton/Art", c = "warrior" }, { p = "/MageButton/Art", c = "magician" }, { p = "/BanditButton/Art", c = "rogue" } }
local arts = { { p = "/WarriorButton/Art", c = "warrior" }, { p = "/MageButton/Art", c = "magician" }, { p = "/BanditButton/Art", c = "bandit" } }
for i = 1, #arts do
local e = _EntityService:GetEntityByPath(base .. arts[i].p)
if e ~= nil and e.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits[arts[i].c] ~= nil then
e.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits[arts[i].c]
end
end
local btns = { { p = "/WarriorButton", c = "warrior" }, { p = "/MageButton", c = "magician" }, { p = "/BanditButton", c = "rogue" } }
local btns = { { p = "/WarriorButton", c = "warrior" }, { p = "/MageButton", c = "magician" }, { p = "/BanditButton", c = "bandit" } }
for i = 1, #btns do
local e = _EntityService:GetEntityByPath(base .. btns[i].p)
if e ~= nil then
@@ -44,9 +44,9 @@ if self.SelectedClass == "warrior" then
eng = "Warrior"
btnName = "/WarriorButton"
desc = "직업군 · 모험가" .. nl .. "방어를 쌓고 버티다 강하게 역공하는 단단한 탱커."
elseif self.SelectedClass == "rogue" then
elseif self.SelectedClass == "bandit" then
name = "도적"
eng = "Rogue"
eng = "Thief"
btnName = "/BanditButton"
desc = "직업군 · 모험가" .. nl .. "표창 난사와 독으로 빠르게 몰아치는 민첩한 직업."
elseif self.SelectedClass == "magician" then
@@ -65,7 +65,7 @@ end
self:SetText(base .. "/SelectedClass", name)
self:SetText(base .. "/SelectedClass/SelectedClassEng", eng)
self:SetText(base .. "/SelectedClassStatus", desc)`),
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "rogue" and self.SelectedClass ~= "magician" then
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "bandit" and self.SelectedClass ~= "magician" then
self:SetText("/ui/SelectUIGroup/CharacterSelectHud/SelectedClassStatus", "직업을 먼저 선택하세요")
return
end

View File

@@ -3,14 +3,6 @@ import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_
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';
export const combatMethods = [
method('CanPlayCardNow', `if c == nil then
return false
end
if c.playableWhenDrawPileEmpty == true and self.DrawPile ~= nil and #self.DrawPile > 0 then
self:Toast("뽑을 카드 더미가 비어 있을 때만 사용할 수 있습니다.")
return false
end
return true`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'boolean'),
method('PlayCard', `if self:IsDiscardSelecting() == true then
self:SelectDiscardSlot(slot)
return
@@ -19,10 +11,6 @@ if self:IsRetainSelecting() == true then
self:SelectRetainSlot(slot)
return
end
if self:IsReserveSelecting() == true then
self:SelectReserveSlot(slot)
return
end
if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
return
end
@@ -38,93 +26,19 @@ if c == nil then
return
end
if c.unplayable == true then
self:Toast("사용할 수 없는 카드입니다")
self:Toast("사용할 수 없는 카드입니다")
return
end
if self:CanPlayCardNow(c) ~= true then
return
end
local cost = c.cost or 0
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
if c.kind == "Skill" and c.useAllEnergy ~= true and self.NextSkillCostZero == true then
cost = 0
skillFree = true
end
if c.kind == "Skill" and c.useAllEnergy ~= true and self.SkillCostReductionThisTurn ~= nil and self.SkillCostReductionThisTurn > 0 then
cost = math.max(0, cost - self.SkillCostReductionThisTurn)
end
if c.useAllEnergy ~= true and self.CombatCardCostReduction ~= nil and self.CombatCardCostReduction[cardId] ~= nil then
cost = math.max(0, cost - self.CombatCardCostReduction[cardId])
end
if c.kind == "Skill" and self.NextSkillRepeatCount ~= nil and self.NextSkillRepeatCount > 0 then
skillRepeat = self.NextSkillRepeatCount
end
if self.Energy < cost then
if self.Energy < c.cost then
self:Toast("에너지가 부족합니다")
return
end
self.Energy = self.Energy - cost
self.ActiveKillReward = c.rewardOnKill or 0
self.ActiveKillMaxHpGain = c.maxHpOnKill or 0
self.Energy = self.Energy - c.cost
self:ResolveCardEffects(cardId, c, false)
table.remove(self.Hand, slot)
self:ResolveCardEffects(cardId, slot, c, false, cost)
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, slot, c, false, cost)
applyCardPlayHooks()
end
end
if c.kind == "Attack" then
self.TurnAttackCardsPlayed = (self.TurnAttackCardsPlayed or 0) + 1
end
if skillFree == true then
if c.nextSkillCostZero ~= true then
self.NextSkillCostZero = false
end
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
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
@@ -134,9 +48,6 @@ self:RenderCombat()
if self:BeginDiscardSelection(c) == true then
return
end
if self:BeginReserveSelection(c) == true then
return
end
self:RenderHand(false)
self:RenderPiles()
self:RenderCombat()
@@ -145,8 +56,6 @@ self:CheckCombatEnd()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0,
self:SelectDiscardSlot(slot)
elseif self:IsRetainSelecting() == true then
self:SelectRetainSlot(slot)
elseif self:IsReserveSelecting() == true then
self:SelectReserveSlot(slot)
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('FindMonsterAtTouch', `local best = 0
local bestDist = 200
@@ -237,10 +146,6 @@ if self:IsRetainSelecting() == true then
self:SelectRetainSlot(slot)
return
end
if self:IsReserveSelecting() == true then
self:SelectReserveSlot(slot)
return
end
if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
return
end
@@ -275,7 +180,7 @@ end`, [
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
]),
method('Toast', `log(message)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'message' }]),
method('DealDamageToTarget', `local m = self.Monsters[self.TargetIndex]
method('DealDamageToTarget', `local m = self.Monsters[self.TargetIndex]
if m == nil or m.alive ~= true then
m = nil
for i = 1, #self.Monsters do
@@ -283,165 +188,29 @@ if m == nil or m.alive ~= true then
end
end
if m == nil then
return false
return
end
local dmg = amount
if m.vuln > 0 then
dmg = math.floor(dmg * 1.5)
end
if m.weak > 0 and self.ActiveAttackDamageVsWeakMultiplier ~= nil and self.ActiveAttackDamageVsWeakMultiplier > 1 then
dmg = math.floor(dmg * self.ActiveAttackDamageVsWeakMultiplier)
end
if m.block > 0 and pierce ~= true then
local absorbed = math.min(m.block, dmg)
m.block = m.block - absorbed
dmg = dmg - absorbed
end
m.hp = m.hp - dmg
if dmg > 0 then
local poison = self:AddPowerFieldTotal("attackPoison")
if poison ~= nil and poison > 0 then
self:ApplyPoisonToMonster(m, poison)
end
end
self:MonsterHitMotion(m.slot)
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`, [
end`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
], 0, 'boolean'),
method('DealDirectDamageToTarget', `local m = self.Monsters[self.TargetIndex]
if m == nil or m.alive ~= true then
m = nil
for i = 1, #self.Monsters do
if self.Monsters[i].alive == true then m = self.Monsters[i]; self.TargetIndex = i; break end
end
end
if m == nil then
return false
end
m.hp = m.hp - amount
self:ShowDmgPop(m.slot, amount)
self:MonsterHitMotion(m.slot)
local killed = false
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
killed = true
end
return killed`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
], 0, 'boolean'),
method('DealDirectDamageToRandomMonster', `local alive = {}
for i = 1, #self.Monsters do
local m = self.Monsters[i]
if m ~= nil and m.alive == true then
table.insert(alive, m)
end
end
if #alive <= 0 then
return false
end
local m = alive[math.random(1, #alive)]
if m == nil then
return false
end
m.hp = m.hp - amount
self:ShowDmgPop(m.slot, amount)
self:MonsterHitMotion(m.slot)
local killed = false
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
killed = true
end
return killed`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
], 0, 'boolean'),
method('ApplyPoisonToMonster', `if target == nil or target.alive ~= true or amount == nil or amount <= 0 then
return
end
if target.artifact ~= nil and target.artifact > 0 then
target.artifact = target.artifact - 1
return
end
target.poison = (target.poison or 0) + amount
self.PoisonApplicationsThisCombat = (self.PoisonApplicationsThisCombat or 0) + 1
local burstEvery = self:AddPowerFieldTotal("poisonApplicationBurstEvery")
local burstDamage = self:AddPowerFieldTotal("poisonApplicationBurstDamage")
if burstEvery ~= nil and burstEvery > 0 and burstDamage ~= nil and burstDamage > 0 then
if (self.PoisonApplicationsThisCombat % burstEvery) == 0 then
self:DealDamageToAllMonsters(burstDamage)
end
end`, [
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'target' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
]),
method('DealDamageToAllMonsters', `if self.Monsters == nil then
return false
end
local killCount = 0
for i = 1, #self.Monsters do
local m = self.Monsters[i]
if m ~= nil and m.alive == true then
local dmg = amount
if isAttack == true and m.vuln > 0 then
dmg = math.floor(dmg * 1.5)
end
if m.block > 0 then
local absorbed = math.min(m.block, dmg)
m.block = m.block - absorbed
dmg = dmg - absorbed
end
m.hp = m.hp - dmg
if dmg > 0 then
self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + dmg
if isAttack == true then
local poison = self:AddPowerFieldTotal("attackPoison")
if poison ~= nil and poison > 0 then
self:ApplyPoisonToMonster(m, poison)
end
end
end
self:ShowDmgPop(i, dmg)
self:MonsterHitMotion(i)
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
killCount = killCount + 1
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
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`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'isAttack' },
], 0, 'boolean'),
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
self:DealDamageToTarget(damage, pierce)
self.ActiveAttackDamageVsWeakMultiplier = 1
self:RenderCombat()
self:CheckCombatEnd()
return
@@ -467,13 +236,7 @@ _TimerService:SetTimerOnce(function()
if mt ~= nil and mt.alive == true and mt.vuln > 0 then
shown = math.floor(damage * 1.5)
end
if mt ~= nil and mt.alive == true and mt.weak > 0 and self.ActiveAttackDamageVsWeakMultiplier ~= nil and self.ActiveAttackDamageVsWeakMultiplier > 1 then
shown = math.floor(shown * self.ActiveAttackDamageVsWeakMultiplier)
end
local killed = self:DealDamageToTarget(damage, pierce)
self.ActiveKillReward = 0
self.ActiveKillMaxHpGain = 0
self.ActiveAttackDamageVsWeakMultiplier = 1
self:DealDamageToTarget(damage, pierce)
self:ShowDmgPop(targetIndex, shown)
self:RenderCombat()
self:CheckCombatEnd()
@@ -497,7 +260,6 @@ end
_TimerService:SetTimerOnce(function()
if fx ~= nil then fx.Enable = false end
self.FxBusy = false
local killCount = 0
for i = 1, #self.Monsters do
local m = self.Monsters[i]
if m ~= nil and m.alive == true then
@@ -505,33 +267,20 @@ _TimerService:SetTimerOnce(function()
if m.vuln > 0 then
dmg = math.floor(dmg * 1.5)
end
if m.weak > 0 and self.ActiveAttackDamageVsWeakMultiplier ~= nil and self.ActiveAttackDamageVsWeakMultiplier > 1 then
dmg = math.floor(dmg * self.ActiveAttackDamageVsWeakMultiplier)
end
if m.block > 0 then
local absorbed = math.min(m.block, dmg)
m.block = m.block - absorbed
dmg = dmg - absorbed
end
m.hp = m.hp - dmg
if dmg > 0 then
local poison = self:AddPowerFieldTotal("attackPoison")
if poison ~= nil and poison > 0 then
self:ApplyPoisonToMonster(m, poison)
end
end
self:ShowDmgPop(i, dmg)
self:MonsterHitMotion(i)
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
killCount = killCount + 1
end
end
end
self.ActiveKillReward = 0
self.ActiveKillMaxHpGain = 0
self.ActiveAttackDamageVsWeakMultiplier = 1
self:RenderCombat()
self:CheckCombatEnd()
end, 0.35)`, [
@@ -552,32 +301,13 @@ 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
dmg = dmg - absorbed
end
if dmg > 0 and self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 and dmg > 1 then
dmg = 1
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
@@ -623,25 +353,18 @@ local m = self.Monsters[idx]
local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(idx)
self:SetEntityEnabled(base .. "/ActFrame", true)
_TimerService:SetTimerOnce(function()
local poisonTicks = 1
local bonusTicks = self:AddPowerFieldTotal("extraPoisonTicks")
if bonusTicks ~= nil and bonusTicks > 0 then
poisonTicks = poisonTicks + bonusTicks
end
for pt = 1, poisonTicks do
if m.poison ~= nil and m.poison > 0 then
m.hp = m.hp - m.poison
self:ShowDmgPop(idx, m.poison)
self:MonsterHitMotion(idx)
m.poison = m.poison - 1
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
self:RenderCombat()
self:SetEntityEnabled(base .. "/ActFrame", false)
_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)
return
end
if m.poison ~= nil and m.poison > 0 then
m.hp = m.hp - m.poison
self:ShowDmgPop(idx, m.poison)
self:MonsterHitMotion(idx)
m.poison = m.poison - 1
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
self:RenderCombat()
self:SetEntityEnabled(base .. "/ActFrame", false)
_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)
return
end
end
m.block = 0
@@ -650,10 +373,6 @@ _TimerService:SetTimerOnce(function()
if intent.kind == "Attack" then
self:MonsterLunge(idx)
local atk = intent.value + m.str
if self.EnemyStrengthLossThisTurn ~= nil and self.EnemyStrengthLossThisTurn > 0 then
atk = atk - self.EnemyStrengthLossThisTurn
if atk < 0 then atk = 0 end
end
if m.weak > 0 then
atk = math.floor(atk * 0.75)
end
@@ -708,24 +427,10 @@ self.DiscardSelectTotal = 0
self.DiscardPostShiv = 0
self.DiscardShivPerPick = 0
self.RetainSelectActive = false
self.TurnAttackCardsPlayed = 0
self.TurnDiscardedCards = 0
self.ReserveSelectActive = false
self.NextTurnBlock = 0
self.NextTurnDraw = 0
self.NextTurnKeepBlock = false
self.NextTurnAttackMultiplier = 1
self.TurnAttackMultiplier = 1
self.NextTurnSelectPrompt = ""
self.NextTurnSelectCopies = 0
self.NextTurnAddCards = {}
self:UpdateDiscardPrompt()
self:RenderHand(false)
self:RenderPiles()`),
method('CheckCombatEnd', `if self.CombatOver == true then
return
end
local anyAlive = false
method('CheckCombatEnd', `local anyAlive = false
for i = 1, #self.Monsters do
if self.Monsters[i].alive == true then anyAlive = true; break end
end
@@ -750,7 +455,7 @@ if anyAlive == false then
end
end
if node ~= nil and node.type == "boss" then
if self:CanAdvanceJob() == true and self.Floor < self.RunLength then
if self.PlayerJob == "" and self.Floor < self.RunLength then
self:ShowJobChoice()
else
if self.PlayerJob ~= "" then self:AwardSouls(1) end

View File

@@ -10,11 +10,7 @@ for i = #list, 2, -1 do
\tlocal j = math.random(1, i)
\tlist[i], list[j] = list[j], list[i]
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]),
method('BindButtons', `if self.ButtonsBound == true then
return
end
self.ButtonsBound = true
local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton")
method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton")
if endTurn ~= nil and (endTurn.ButtonComponent ~= nil or endTurn:AddComponent("ButtonComponent") ~= nil) then
if self.EndTurnHandler ~= nil then
endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)
@@ -229,44 +225,14 @@ for i = 1, 3 do
end`),
method('StartPlayerTurn', `self.Turn = self.Turn + 1
self.RetainSelectActive = false
self.ReserveSelectActive = false
self.TurnAttackCardsPlayed = 0
self.TurnDiscardedCards = 0
self.TurnCardsPlayedThisTurn = 0
self.DamageDealtThisTurn = 0
self.NextTurnSelectCopies = 0
self.NextTurnSelectPrompt = ""
self.SkillCostReductionThisTurn = 0
self:UpdateDiscardPrompt()
self.Energy = self.MaxEnergy
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
self.PlayerBlock = 0
if self.ClayBlockNext > 0 then
self.PlayerBlock = self.PlayerBlock + self.ClayBlockNext
self.ClayBlockNext = 0
end
self.TurnAttackMultiplier = self.NextTurnAttackMultiplier or 1
self.NextTurnAttackMultiplier = 1
self.CardsDrawnThisCombat = self.CardsDrawnThisCombat or 0
self.ShivFirstDamageBonusUsed = false
self.ActiveAttackDamageVsWeakMultiplier = 1
self.DrawDamageThisTurn = 0
self.DrawPoisonThisTurn = 0
self.ShivAoeThisCombat = false
self.ZeroCostCardIdsThisTurn = {}
self.SkillSlyOnPlayCards = self.SkillSlyOnPlayCards or {}
self.TurnSkillSlyCards = {}
self.EnemyStrengthLossThisTurn = 0
self.HandCostZeroThisTurn = false
self.DrawDisabledThisTurn = false
local powerTurnDraw = 0
local powerTurnDiscard = 0
if self.PlayerPowers ~= nil then
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
@@ -277,79 +243,16 @@ 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
local tm = self.Monsters[j]
if tm ~= nil and tm.alive == true then
self:ApplyPoisonToMonster(tm, pc.value)
end
end
end
elseif pc.powerEffect == "damagePerTurn" 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)
end
if pc.turnStartDraw ~= nil then
powerTurnDraw = powerTurnDraw + pc.turnStartDraw
end
if pc.turnStartDiscard ~= nil then
powerTurnDiscard = powerTurnDiscard + pc.turnStartDiscard
end
end
end
end
if self.NextTurnBlock ~= nil and self.NextTurnBlock > 0 then
self:AddCardBlock(self.NextTurnBlock)
self.NextTurnBlock = 0
end
if self.NextTurnAddCards ~= nil then
for i = 1, #self.NextTurnAddCards do
local entry = self.NextTurnAddCards[i]
if entry ~= nil and entry.cardId ~= nil and entry.amount ~= nil and entry.amount > 0 then
self:AddCardsToHand(entry.cardId, entry.amount)
end
end
self.NextTurnAddCards = {}
end
local drawN = 5 + (self.NextTurnDraw or 0) + powerTurnDraw
self.NextTurnDraw = 0
self:DrawCards(drawN)
self:DrawCards(5)
self:RenderHand(true)
self:RenderCombat()
if powerTurnDiscard > 0 then
self:BeginDiscardSelection({ discard = math.min(powerTurnDiscard, #self.Hand) })
return
end
self:RenderCombat()`),
method('PrepareCombatDrawPile', `if self.DrawPile == nil or self.Cards == nil then
return
end
local rest = {}
local innate = {}
for i = 1, #self.DrawPile do
local cardId = self.DrawPile[i]
local c = self.Cards[cardId]
if c ~= nil and c.innate == true then
table.insert(innate, cardId)
else
table.insert(rest, cardId)
end
end
self.DrawPile = {}
for i = 1, #rest do
table.insert(self.DrawPile, rest[i])
end
for i = 1, #innate do
table.insert(self.DrawPile, innate[i])
end`, []),
method('HasPowerEffect', `if self.PlayerPowers == nil then
return false
end
@@ -360,27 +263,6 @@ for i = 1, #self.PlayerPowers do
end
end
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'effect' }], 0, 'boolean'),
method('HasPowerField', `if self.PlayerPowers == nil then
return false
end
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil and pc[field] ~= nil and pc[field] ~= 0 then
return true
end
end
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'field' }], 0, 'boolean'),
method('AddPowerFieldTotal', `local total = 0
if self.PlayerPowers == nil then
return total
end
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil and pc[field] ~= nil then
total = total + pc[field]
end
end
return total`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'field' }], 0, 'number'),
method('ShouldOfferRetain', `if self:HasPowerEffect("retainOne") ~= true then
return false
end
@@ -409,19 +291,12 @@ if self:IsRetainSelecting() == true then
self:FinishPlayerTurn(0)
return
end
if self:IsReserveSelecting() == true then
self:Toast("예약할 카드를 먼저 선택하세요")
return
end
if self:ShouldOfferRetain() == true then
self:BeginRetainSelection()
return
end
self:FinishPlayerTurn(0)`),
method('FinishPlayerTurn', `self.RetainSelectActive = false
self.ReserveSelectActive = false
self.NextTurnSelectCopies = 0
self.NextTurnSelectPrompt = ""
self:UpdateDiscardPrompt()
local burn = 0
for bi = 1, #self.Hand do
@@ -438,38 +313,20 @@ local kept = {}
for i = 1, #self.Hand do
\tlocal cardId = self.Hand[i]
\tlocal c = self.Cards[cardId]
\tif c ~= nil and (c.retain == true or (c.class == "shiv" and self:HasPowerField("shivRetain") == true) or i == retainSlot) then
\tif c ~= nil and (c.retain == true or i == retainSlot) then
\t\ttable.insert(kept, cardId)
\telse
\t\ttable.insert(self.DiscardPile, cardId)
\tend
end
self.Hand = kept
if self.PlayerPowers ~= nil then
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil and pc.endTurnDexLoss ~= nil and pc.endTurnDexLoss > 0 then
self.PlayerDex = self.PlayerDex - pc.endTurnDexLoss
if self.PlayerDex < 0 then self.PlayerDex = 0 end
end
end
end
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
self.PlayerIntangible = self.PlayerIntangible - 1
if self.PlayerIntangible < 0 then self.PlayerIntangible = 0 end
end
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
self:RenderHand(false)
self:RenderPiles()
self.TurnSkillSlyCards = {}
self:EnemyTurn()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'retainSlot' }]),
method('DrawCards', `local drawnSlots = {}
local drawnCards = {}
method('DrawCards', `local drawnSlots = {}
local drewAny = false
if self.DrawDisabledThisTurn == true then
\treturn drawnCards
end
for i = 1, amount do
\tif #self.DrawPile <= 0 then
\t\tself:RecycleDiscardIntoDraw()
@@ -478,19 +335,13 @@ for i = 1, amount do
\t\tbreak
\tend
\tlocal cardId = table.remove(self.DrawPile)
\ttable.insert(drawnCards, cardId)
\tself.CardsDrawnThisCombat = (self.CardsDrawnThisCombat or 0) + 1
\tself:ApplyDrawTrigger()
\tif #self.Hand >= 10 then
\t\ttable.insert(self.DiscardPile, cardId)
\t\tself:TriggerSly(cardId)
\telse
\t\ttable.insert(self.Hand, cardId)
\t\tlocal autoPlayed = self:TriggerDrawnCardAutoPlay(cardId)
\t\tif autoPlayed ~= true then
\t\t\tdrewAny = true
\t\t\ttable.insert(drawnSlots, #self.Hand)
\t\tend
\t\tdrewAny = true
\t\ttable.insert(drawnSlots, #self.Hand)
\tend
end
self:RenderPiles()
@@ -503,12 +354,10 @@ 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'),
]),
method('AddCardsToHand', `if self.Hand == nil then
self.Hand = {}
end

View File

@@ -77,7 +77,7 @@ if thiefTab ~= nil and (thiefTab.ButtonComponent ~= nil or thiefTab:AddComponent
thiefTab:DisconnectEvent(ButtonClickEvent, self.ThiefDeckTabHandler)
self.ThiefDeckTabHandler = nil
end
self.ThiefDeckTabHandler = thiefTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("rogue") end)
self.ThiefDeckTabHandler = thiefTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("bandit") end)
end
local mageTab = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/MageTab")
if mageTab ~= nil and (mageTab.ButtonComponent ~= nil or mageTab:AddComponent("ButtonComponent") ~= nil) then
@@ -101,8 +101,8 @@ end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], N
return
end
local className = self.SelectedClass
if className ~= "warrior" and className ~= "magician" and className ~= "rogue" then
className = "rogue"
if className ~= "warrior" and className ~= "magician" and className ~= "bandit" then
className = "bandit"
end
self.CodexMode = false
self.ClassDeckMode = true
@@ -119,30 +119,32 @@ self:Toast("테스트 카드 추가 모드")`),
end
self.ClassDeckCards = {}
self.ClassDeckTitle = "직업 덱"
if className ~= "warrior" and className ~= "magician" and className ~= "rogue" then
className = "rogue"
if className ~= "warrior" and className ~= "magician" and className ~= "bandit" then
className = "bandit"
end
self.ClassDeckClass = className
local allowed = {}
local group = nil
if self.ClassGroups ~= nil then
group = self.ClassGroups[className]
end
if group == nil then
group = { className }
end
for i = 1, #group do
allowed[group[i]] = true
end
if className == "warrior" then
allowed["warrior"] = true
allowed["fighter"] = true
allowed["page"] = true
allowed["spearman"] = true
self.ClassDeckTitle = "전사 전체 덱"
elseif className == "magician" then
allowed["magician"] = true
allowed["firepoison"] = true
allowed["icelightning"] = true
allowed["cleric"] = true
self.ClassDeckTitle = "마법사 전체 덱"
else
allowed["bandit"] = true
allowed["shiv"] = true
allowed["poisoner"] = true
allowed["trickster"] = true
self.ClassDeckTitle = "도적 전체 덱"
end
for id, c in pairs(self.Cards) do
if c ~= nil and c.curse ~= true and c.token ~= true and allowed[c.class] == true then
if c ~= nil and c.curse ~= true and allowed[c.class] == true then
table.insert(self.ClassDeckCards, id)
end
end
@@ -160,7 +162,7 @@ self:RenderAllDeck()
self:RenderClassDeckTabs()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' }]),
method('RenderClassDeckTabs', `local tabs = {
{ path = "/ui/DeckUIGroup/DeckAllHud/WarriorTab", cls = "warrior" },
{ path = "/ui/DeckUIGroup/DeckAllHud/ThiefTab", cls = "rogue" },
{ path = "/ui/DeckUIGroup/DeckAllHud/ThiefTab", cls = "bandit" },
{ path = "/ui/DeckUIGroup/DeckAllHud/MageTab", cls = "magician" },
}
for i = 1, #tabs do

View File

@@ -3,42 +3,6 @@ import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_
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';
export const handMethods = [
method('ApplyDrawTrigger', `if self.Monsters == nil then
return
end
local drawDamage = self:AddPowerFieldTotal("drawDamage") + (self.DrawDamageThisTurn or 0)
local drawPoison = self:AddPowerFieldTotal("drawPoison") + (self.DrawPoisonThisTurn or 0)
if (drawDamage ~= nil and drawDamage > 0) or (drawPoison ~= nil and drawPoison > 0) then
for mi = 1, #self.Monsters do
local m2 = self.Monsters[mi]
if m2 ~= nil and m2.alive == true then
local dmg = drawDamage or 0
if m2.vuln > 0 then
dmg = math.floor(dmg * 1.5)
end
if m2.block > 0 then
local absorbed = math.min(m2.block, dmg)
m2.block = m2.block - absorbed
dmg = dmg - absorbed
end
if drawPoison ~= nil and drawPoison > 0 then
self:ApplyPoisonToMonster(m2, drawPoison)
end
if dmg > 0 then
m2.hp = m2.hp - dmg
self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + dmg
end
self:ShowDmgPop(mi, dmg)
self:MonsterHitMotion(mi)
if m2.hp <= 0 then
m2.hp = 0
self:KillMonster(m2.slot)
end
end
end
self:RenderCombat()
self:CheckCombatEnd()
end`),
method('GetHandSlotX', `local n = 0
if self.Hand ~= nil then
n = #self.Hand
@@ -96,7 +60,7 @@ if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
end
self:SetText(base .. "/Cost", string.format("%d", c.cost))
self:SetText(base .. "/Name", c.name)
self:SetText(base .. "/Desc", self:FormatCardDescription(c.desc))
self:SetText(base .. "/Desc", c.desc)
local art = _EntityService:GetEntityByPath(base .. "/Art")
if art ~= nil then
if c.image ~= nil and c.image ~= "" then
@@ -305,357 +269,11 @@ end, 1 / 60)`, [
if amount > 0 and self.PlayerDex ~= nil then
amount = amount + self.PlayerDex
end
if self.BlockGainMultiplier ~= nil and self.BlockGainMultiplier > 1 then
amount = amount * self.BlockGainMultiplier
end
if amount < 0 then
amount = 0
end
self.PlayerBlock = self.PlayerBlock + amount
return amount`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }], 0, 'number'),
method('CountOtherHandSkills', `if self.Hand == nil then
return 0
end
local n = 0
for i = 1, #self.Hand do
if i ~= slot then
local hc = self.Cards[self.Hand[i]]
if hc ~= nil and hc.kind == "Skill" then
n = n + 1
end
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
if otherHand < 0 then otherHand = 0 end
end
if c.damagePerOtherHandCard ~= nil then
base2 = base2 + otherHand * c.damagePerOtherHandCard
end
if c.damagePerAttackPlayedThisTurn ~= nil then
base2 = base2 + (self.TurnAttackCardsPlayed or 0) * c.damagePerAttackPlayedThisTurn
end
if c.damagePerDiscardedThisTurn ~= nil then
base2 = base2 + (self.TurnDiscardedCards or 0) * c.damagePerDiscardedThisTurn
end
if c.damagePerSkillInHand ~= nil then
base2 = base2 + self:CountOtherHandSkills(slot) * c.damagePerSkillInHand
end
if c.damagePerCardDrawnThisCombat ~= nil then
base2 = base2 + (self.CardsDrawnThisCombat or 0) * c.damagePerCardDrawnThisCombat
end
if c.kind == "Attack" and (self.TurnCardsPlayedThisTurn or 0) == 0 and c.firstCardDamageBonus ~= nil then
base2 = base2 + c.firstCardDamageBonus
end
if c.class == "shiv" then
if self:HasPowerField("shivDamageBonus") == true then
base2 = base2 + self:AddPowerFieldTotal("shivDamageBonus")
end
if self.ShivFirstDamageBonusUsed ~= true and self:HasPowerField("firstShivDamageBonus") == true then
base2 = base2 + self:AddPowerFieldTotal("firstShivDamageBonus")
end
end
if base2 < 0 then
base2 = 0
end
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
@@ -668,9 +286,6 @@ end
if self.PlayerWeak > 0 then
dmg = math.floor(dmg * 0.75)
end
if self.TurnAttackMultiplier ~= nil and self.TurnAttackMultiplier > 1 then
dmg = dmg * self.TurnAttackMultiplier
end
if dmg > 0 and dmg < 5 and self:HasRelic("boot") then
dmg = 5
end
@@ -678,197 +293,32 @@ if dmg < 0 then
dmg = 0
end
return dmg`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }], 0, 'number'),
method('QueueNextTurnAddCard', `if cardId == nil or cardId == "" or amount == nil or amount <= 0 then
method('ResolveCardEffects', `if c == nil then
return
end
if self.NextTurnAddCards == nil then
self.NextTurnAddCards = {}
end
for i = 1, #self.NextTurnAddCards do
local entry = self.NextTurnAddCards[i]
if entry ~= nil and entry.cardId == cardId then
entry.amount = (entry.amount or 0) + amount
return
end
end
table.insert(self.NextTurnAddCards, { cardId = cardId, amount = amount })`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
]),
method('QueueNextTurnEffects', `if c == nil then
return
end
if c.nextTurnBlock ~= nil then
self.NextTurnBlock = (self.NextTurnBlock or 0) + c.nextTurnBlock
end
if c.nextTurnDraw ~= nil then
self.NextTurnDraw = (self.NextTurnDraw or 0) + c.nextTurnDraw
end
if c.nextTurnKeepBlock == true then
self.NextTurnKeepBlock = true
end
if c.nextTurnAttackMultiplier ~= nil and c.nextTurnAttackMultiplier > 0 then
local cur = self.NextTurnAttackMultiplier or 1
self.NextTurnAttackMultiplier = cur * c.nextTurnAttackMultiplier
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }]),
method('ResolveCardEffects', `if c == nil then
return
end
if c.blockGainMultiplier ~= nil and c.blockGainMultiplier > 0 then
self.BlockGainMultiplier = (self.BlockGainMultiplier or 1) * c.blockGainMultiplier
end
if c.nextSkillCostZero == true then
self.NextSkillCostZero = true
end
if c.nextSkillRepeatCount ~= nil and c.nextSkillRepeatCount > 0 then
self.NextSkillRepeatCount = (self.NextSkillRepeatCount or 0) + c.nextSkillRepeatCount
end
if c.skillCostReductionThisTurn ~= nil and c.skillCostReductionThisTurn > 0 then
self.SkillCostReductionThisTurn = (self.SkillCostReductionThisTurn or 0) + c.skillCostReductionThisTurn
end
if c.handCostZeroThisTurn == true then
self.HandCostZeroThisTurn = true
end
if c.drawDisabledThisTurn == true then
self.DrawDisabledThisTurn = true
end
if c.drawDamage ~= nil and c.drawDamage > 0 and c.kind ~= "Power" then
self.DrawDamageThisTurn = (self.DrawDamageThisTurn or 0) + c.drawDamage
end
if c.drawPoison ~= nil and c.drawPoison > 0 and c.kind ~= "Power" then
self.DrawPoisonThisTurn = (self.DrawPoisonThisTurn or 0) + c.drawPoison
end
if c.shivAoe == true and c.kind ~= "Power" then
self.ShivAoeThisCombat = true
end
if c.skillSlyOnPlay == true and c.kind == "Skill" then
if self.SkillSlyOnPlayCards == nil then
self.SkillSlyOnPlayCards = {}
end
self.SkillSlyOnPlayCards[cardId] = true
end
if c.turnHandSlyCount ~= nil and c.turnHandSlyCount > 0 then
if self.TurnSkillSlyCards == nil then
self.TurnSkillSlyCards = {}
end
local picked = 0
if self.Hand ~= nil then
for i = 1, #self.Hand do
local hid = self.Hand[i]
if hid ~= nil and hid ~= cardId then
local hc = self.Cards[hid]
if hc ~= nil and hc.kind == "Skill" and self.TurnSkillSlyCards[hid] ~= true and self.SkillSlyOnPlayCards[hid] ~= true and hc.sly ~= true then
self.TurnSkillSlyCards[hid] = true
picked = picked + 1
if picked >= c.turnHandSlyCount then
break
end
end
end
end
end
end
local xEnergy = energySpent or 0
local weakAmount = c.weak or 0
local vulnAmount = c.vuln or 0
local poisonAmount = c.poison or 0
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 or c.damageFromCurrentBlock ~= nil then
if c.damage ~= nil then
self:PlayerAttackMotion()
local baseDmg = self:AttackBaseForCard(slot, c)
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
local total = 0
local hitN = c.hits or 1
if c.otherHandAtLeast ~= nil and c.bonusHitsWhenOtherHandAtLeast ~= nil then
local otherHand = 0
if self.Hand ~= nil then
otherHand = #self.Hand - 1
if otherHand < 0 then otherHand = 0 end
end
if otherHand >= c.otherHandAtLeast then
hitN = hitN + c.bonusHitsWhenOtherHandAtLeast
end
end
for h = 1, hitN do
total = total + self:CalcPlayerAttack(baseDmg)
total = total + self:CalcPlayerAttack(c.damage)
end
local useAoe = c.aoe == true
if c.class == "shiv" and (self.ShivAoeThisCombat == true or self:HasPowerField("shivAoe") == true) then
useAoe = true
if c.aoe == true then
self:PlayAoeFx(c.fx or c.image, total)
else
self:PlayAttackFx(self.TargetIndex, c.fx or c.image, total, c.pierce == true)
end
if c.class == "shiv" and self.ShivFirstDamageBonusUsed ~= true and self:HasPowerField("firstShivDamageBonus") == true then
self.ShivFirstDamageBonusUsed = true
end
local function countAliveMonsters()
local n = 0
if self.Monsters ~= nil then
for mi = 1, #self.Monsters do
local om = self.Monsters[mi]
if om ~= nil and om.alive == true then n = n + 1 end
end
end
return n
end
local function randomAliveMonsterIndex()
local alive = {}
if self.Monsters ~= nil then
for mi = 1, #self.Monsters do
local om = self.Monsters[mi]
if om ~= nil and om.alive == true then
table.insert(alive, mi)
end
end
end
if #alive <= 0 then
return 0
end
return alive[math.random(1, #alive)]
end
local function resolveAttackRound()
local roundKilled = false
if useAoe == true then
local killed = self:DealDamageToAllMonsters(total, true)
if killed == true then roundKilled = true end
elseif c.randomTargetEachHit == true then
for h = 1, hitN do
local targetIdx = randomAliveMonsterIndex()
if targetIdx ~= nil and targetIdx > 0 then
local prev = self.TargetIndex
self.TargetIndex = targetIdx
local killed = self:DealDamageToTarget(total / hitN, c.pierce == true)
self.TargetIndex = prev
if killed == true then roundKilled = true end
end
end
else
local killed = self:DealDamageToTarget(total, c.pierce == true)
if killed == true then roundKilled = true end
end
return roundKilled
end
local totalDamage = 0
local roundKilled = false
repeat
roundKilled = resolveAttackRound()
totalDamage = totalDamage + total
until c.repeatOnKill ~= true or roundKilled ~= true or countAliveMonsters() <= 0
self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + totalDamage
end
if c.block ~= nil then
self:AddCardBlock(c.block + (self.HolyChargeCount or 0) * (c.blockPerHolyCharge or 0))
self:AddCardBlock(c.block)
end
if free ~= true then
self:ApplyRelics("cardPlayed")
end
elseif c.kind == "Skill" then
if c.block ~= nil then
self:AddCardBlock(c.block + (self.HolyChargeCount or 0) * (c.blockPerHolyCharge or 0))
self:AddCardBlock(c.block)
end
elseif c.kind == "Power" then
if free ~= true then
@@ -888,173 +338,42 @@ if c.selfVuln ~= nil then
self.PlayerVuln = self.PlayerVuln + c.selfVuln
end
if c.heal ~= nil then
local healAmount = c.heal + (self.HolyChargeCount or 0) * (c.healPerHolyCharge or 0)
self.PlayerHp = math.min(self.PlayerHp + healAmount, self.PlayerMaxHp)
self.PlayerHp = math.min(self.PlayerHp + c.heal, 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
self.TurnCardsPlayedThisTurn = (self.TurnCardsPlayedThisTurn or 0) + 1
if c.blockPerDamageDealtThisTurn ~= nil and c.blockPerDamageDealtThisTurn > 0 then
self:AddCardBlock((self.DamageDealtThisTurn or 0) * c.blockPerDamageDealtThisTurn)
end
self:QueueNextTurnEffects(c)
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
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil or c.xWeakPerEnergy ~= nil or c.affectsAllEnemies == true or c.removeEnemyBlock == true or c.removeEnemyArtifact == true or (c.enemyStrengthLossThisTurn ~= nil and c.enemyStrengthLossThisTurn > 0) then
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then
local tm = self.Monsters[self.TargetIndex]
if tm == nil or tm.alive ~= true then
for i = 1, #self.Monsters do
if self.Monsters[i].alive == true then tm = self.Monsters[i]; self.TargetIndex = i; break end
end
end
local targets = {}
if c.affectsAllEnemies == true and self.Monsters ~= nil then
for mi = 1, #self.Monsters do
local om = self.Monsters[mi]
if om ~= nil and om.alive == true then
table.insert(targets, om)
end
end
elseif tm ~= nil and tm.alive == true then
table.insert(targets, tm)
end
if c.enemyStrengthLossThisTurn ~= nil and c.enemyStrengthLossThisTurn > 0 then
self.EnemyStrengthLossThisTurn = (self.EnemyStrengthLossThisTurn or 0) + c.enemyStrengthLossThisTurn
end
for ti = 1, #targets do
local target = targets[ti]
if target ~= nil and target.alive == true then
if c.removeEnemyBlock == true then
target.block = 0
end
if c.removeEnemyArtifact == true then
target.artifact = 0
end
if weakAmount ~= nil and weakAmount > 0 then
if target.artifact ~= nil and target.artifact > 0 then
target.artifact = target.artifact - 1
else
target.weak = target.weak + weakAmount
end
end
if poisonAmount ~= nil and poisonAmount > 0 then
if c.poisonIfTargetPoisoned ~= true or (target.poison ~= nil and target.poison > 0) then
local poisonHits = c.poisonHits or 1
for pi = 1, poisonHits do
local target2 = target
if c.poisonRandomTargets == true and self.Monsters ~= nil then
local alive = {}
for mi = 1, #self.Monsters do
local om = self.Monsters[mi]
if om ~= nil and om.alive == true then
table.insert(alive, om)
end
end
if #alive > 0 then
target2 = alive[math.random(#alive)]
end
end
if target2 ~= nil and target2.alive == true then
self:ApplyPoisonToMonster(target2, poisonAmount)
end
end
end
end
if vulnAmount ~= nil and vulnAmount > 0 then
if target.artifact ~= nil and target.artifact > 0 then
target.artifact = target.artifact - 1
else
target.vuln = target.vuln + vulnAmount
if self:HasRelic("championBelt") then
target.weak = target.weak + 1
end
end
if tm ~= nil and tm.alive == true then
if c.weak ~= nil then tm.weak = tm.weak + c.weak end
if c.poison ~= nil then tm.poison = (tm.poison or 0) + c.poison end
if c.vuln ~= nil then
tm.vuln = tm.vuln + c.vuln
if self:HasRelic("championBelt") then
tm.weak = tm.weak + 1
end
end
end
end
local drawnCards = {}
if c.draw ~= nil then
drawnCards = self:DrawCards(c.draw, true) or {}
end
if c.drawUntilHandSize ~= nil and c.drawUntilHandSize > 0 then
local currentHand = 0
if self.Hand ~= nil then
currentHand = #self.Hand
if slot ~= nil and slot > 0 and self.Hand[slot] == cardId then
currentHand = currentHand - 1
end
end
local need = c.drawUntilHandSize - currentHand
if need > 0 then
local moreDrawnCards = self:DrawCards(need, true) or {}
for i = 1, #moreDrawnCards do
table.insert(drawnCards, moreDrawnCards[i])
end
end
end
if c.drawSkillBlock ~= nil and c.drawSkillBlock > 0 then
for i = 1, #drawnCards do
local drawnCard = self.Cards[drawnCards[i]]
if drawnCard ~= nil and drawnCard.kind == "Skill" then
self:AddCardBlock(c.drawSkillBlock)
end
end
self:DrawCards(c.draw, true)
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' },
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'free' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'energySpent' },
]),
method('TriggerSly', `local c = self.Cards[cardId]
if c == nil then
if c == nil or c.sly ~= true then
return
end
if c.sly ~= true then
local onPlay = self.SkillSlyOnPlayCards ~= nil and self.SkillSlyOnPlayCards[cardId] == true
local tempSly = self.TurnSkillSlyCards ~= nil and self.TurnSkillSlyCards[cardId] == true
if onPlay ~= true and tempSly ~= true then
return
end
end
self:Toast("교활 발동: " .. c.name)
self:ResolveCardEffects(cardId, 0, c, true, 0)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }]),
self:ResolveCardEffects(cardId, c, true)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }]),
method('DiscardHandCard', `if self.Hand == nil then
return
end
@@ -1065,7 +384,6 @@ end
local startX = self:GetHandSlotX(slot)
table.remove(self.Hand, slot)
table.insert(self.DiscardPile, cardId)
self.TurnDiscardedCards = (self.TurnDiscardedCards or 0) + 1
if triggerSly == true then
self:TriggerSly(cardId)
end
@@ -1078,7 +396,6 @@ end`, [
]),
method('IsDiscardSelecting', `return self.DiscardSelectRemaining ~= nil and self.DiscardSelectRemaining > 0`, [], 0, 'boolean'),
method('IsRetainSelecting', `return self.RetainSelectActive == true`, [], 0, 'boolean'),
method('IsReserveSelecting', `return self.ReserveSelectActive == true`, [], 0, 'boolean'),
method('UpdateDiscardPrompt', `local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/DiscardPrompt")
if e == nil then
return
@@ -1090,13 +407,6 @@ if self:IsDiscardSelecting() == true then
elseif self:IsRetainSelecting() == true then
self:SetText("/ui/RunUIGroup/CombatHud/DiscardPrompt", "보존할 카드 선택 (턴 종료: 건너뛰기)")
e.Enable = true
elseif self:IsReserveSelecting() == true then
local msg = self.NextTurnSelectPrompt or ""
if msg == "" then
msg = "다음 턴에 예약할 카드를 선택하세요"
end
self:SetText("/ui/RunUIGroup/CombatHud/DiscardPrompt", msg)
e.Enable = true
else
e.Enable = false
end`),
@@ -1117,56 +427,15 @@ self.DiscardSelectRemaining = n
self.DiscardSelectTotal = n
self.DiscardPostShiv = 0
self.DiscardShivPerPick = 0
self.DiscardPostDraw = 0
self.DiscardDrawPerPick = 0
if c.addShiv ~= nil then
self.DiscardPostShiv = c.addShiv
end
if c.addShivPerDiscard == true then
self.DiscardShivPerPick = 1
end
if c.drawPerDiscarded ~= nil and c.drawPerDiscarded > 0 then
self.DiscardDrawPerPick = c.drawPerDiscarded
end
self:UpdateDiscardPrompt()
self:Toast("버릴 카드를 선택하세요")
return true`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'boolean'),
method('BeginReserveSelection', `if c == nil or c.nextTurnSelectHandCard ~= true or c.nextTurnCopies == nil or c.nextTurnCopies <= 0 then
return false
end
if self.Hand == nil or #self.Hand <= 0 then
return false
end
self.ReserveSelectActive = true
self.NextTurnSelectCopies = c.nextTurnCopies
self.NextTurnSelectPrompt = c.nextTurnSelectPrompt or ""
self:UpdateDiscardPrompt()
self:Toast("예약할 카드를 선택하세요")
self:RenderHand(false)
return true`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'boolean'),
method('SelectReserveSlot', `if self:IsReserveSelecting() ~= true then
return false
end
if self.Hand == nil or self.Hand[slot] == nil then
return true
end
local cardId = self.Hand[slot]
local amount = self.NextTurnSelectCopies or 0
self.ReserveSelectActive = false
self.NextTurnSelectCopies = 0
self.NextTurnSelectPrompt = ""
self:UpdateDiscardPrompt()
if amount > 0 and cardId ~= nil then
self:QueueNextTurnAddCard(cardId, amount)
local label = cardId
if self.Cards[cardId] ~= nil and self.Cards[cardId].name ~= nil then
label = self.Cards[cardId].name
end
self:Toast("다음 턴 예약: " .. label .. " " .. self:FormatNumber(amount) .. "장")
end
self:RenderPiles()
self:RenderCombat()
return true`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }], 0, 'boolean'),
method('SelectRetainSlot', `if self:IsRetainSelecting() ~= true then
return false
end
@@ -1188,7 +457,6 @@ for i = 1, n do
table.insert(startXs, self:GetHandSlotX(i))
table.insert(slots, i)
table.insert(self.DiscardPile, cardId)
self.TurnDiscardedCards = (self.TurnDiscardedCards or 0) + 1
end
self.Hand = {}
local shivCount = 0
@@ -1198,8 +466,6 @@ self.DiscardSelectRemaining = 0
self.DiscardSelectTotal = 0
self.DiscardPostShiv = 0
self.DiscardShivPerPick = 0
self.DiscardPostDraw = 0
self.DiscardDrawPerPick = 0
self:UpdateDiscardPrompt()
self:AnimateDiscardCards(cardIds, startXs, slots)
for i = 1, #cardIds do
@@ -1214,9 +480,6 @@ _TimerService:SetTimerOnce(function()
self:RenderHand(false)
self:RenderPiles()
end
if c.drawPerDiscarded ~= nil and c.drawPerDiscarded > 0 then
self:DrawCards(n * c.drawPerDiscarded, true)
end
self:RenderCombat()
self:CheckCombatEnd()
end, 0.22)
@@ -1224,11 +487,8 @@ return true`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes:
method('FinishDiscardSelection', `self.DiscardSelectRemaining = 0
self.DiscardSelectTotal = 0
local shivCount = self.DiscardPostShiv or 0
local drawCount = self.DiscardPostDraw or 0
self.DiscardPostShiv = 0
self.DiscardPostDraw = 0
self.DiscardShivPerPick = 0
self.DiscardDrawPerPick = 0
self:UpdateDiscardPrompt()
local finish = function()
if shivCount > 0 then
@@ -1237,9 +497,6 @@ local finish = function()
self:RenderHand(false)
self:RenderPiles()
end
if drawCount > 0 then
self:DrawCards(drawCount, true)
end
self:RenderCombat()
self:CheckCombatEnd()
end
@@ -1259,9 +516,6 @@ self:DiscardHandCard(slot, true, true)
if discarded ~= nil and self.DiscardShivPerPick ~= nil and self.DiscardShivPerPick > 0 then
self.DiscardPostShiv = (self.DiscardPostShiv or 0) + self.DiscardShivPerPick
end
if discarded ~= nil and self.DiscardDrawPerPick ~= nil and self.DiscardDrawPerPick > 0 then
self.DiscardPostDraw = (self.DiscardPostDraw or 0) + self.DiscardDrawPerPick
end
self.DiscardSelectRemaining = self.DiscardSelectRemaining - 1
if self.DiscardSelectRemaining <= 0 or #self.Hand <= 0 then
self:FinishDiscardSelection(true)

View File

@@ -1,50 +1,9 @@
import { method } from '../lib/codeblock.mjs';
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const jobMethods = [
method('BaseClassLabel', `if classId == "warrior" then
return "전사"
elseif classId == "rogue" then
return "Rogue"
elseif classId == "magician" then
return "마법사"
end
return "플레이어"`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'classId' }], 0, 'string'),
method('CurrentClassId', `if self.PlayerJob ~= nil and self.PlayerJob ~= "" then
return self.PlayerJob
end
return self.SelectedClass or ""`, [], 0, 'string'),
method('GetPlayableClasses', `local current = self:CurrentClassId()
if current == nil or current == "" then
return {}
end
if self.ClassLineages ~= nil and self.ClassLineages[current] ~= nil then
return self.ClassLineages[current]
end
return { current }`, [], 0, 'any'),
method('CanUseClassCard', `if cardClass == nil or cardClass == "" then
return false
end
if cardClass == "curse" then
return true
end
local playable = self:GetPlayableClasses()
for i = 1, #playable do
if playable[i] == cardClass then
return true
end
end
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardClass' }], 0, 'boolean'),
method('CanAdvanceJob', `local current = self:CurrentClassId()
if current == nil or current == "" or self.Jobs == nil then
return false
end
local opts = self.Jobs[current]
return opts ~= nil and #opts > 0`, [], 0, 'boolean'),
method('ShowJobChoice', `if self:CanAdvanceJob() ~= true then
self:ContinueAfterBoss()
return
end
self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false)
method('ShowJobChoice', `self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", false)
self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", true)`),
method('PickJobReward', `self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false)
@@ -61,13 +20,9 @@ if kind == "relic" then
else
self:ShowJobSelect()
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'kind' }]),
method('ShowJobSelect', `local current = self:CurrentClassId()
local opts = nil
if self.Jobs ~= nil then
opts = self.Jobs[current]
end
method('ShowJobSelect', `local opts = self.Jobs[self.SelectedClass]
if opts == nil then
opts = {}
opts = self.Jobs["warrior"]
end
self.JobOpts = opts
for i = 1, 3 do
@@ -77,47 +32,46 @@ for i = 1, 3 do
self:SetEntityEnabled(base, true)
self:SetText(base .. "/Name", o.name)
self:SetText(base .. "/Desc", o.desc)
local sc = nil
if o.starter ~= nil and o.starter ~= "" then
sc = self.Cards[o.starter]
end
local sc = self.Cards[o.starter]
if sc ~= nil then
self:SetText(base .. "/Starter", "대표 카드: " .. sc.name)
else
self:SetText(base .. "/Starter", "대표 카드: 계보 유지")
end
else
self:SetEntityEnabled(base, false)
end
end
self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", true)`),
method('JobLabel', `if self.PlayerJob ~= "" and self.JobMeta ~= nil and self.JobMeta[self.PlayerJob] ~= nil then
return self.JobMeta[self.PlayerJob].name
end
return self:BaseClassLabel(self.SelectedClass)`, [], 0, 'string'),
method('SetJob', `local current = self:CurrentClassId()
local starter = ""
local tier = 2
local opts = {}
if self.Jobs ~= nil and self.Jobs[current] ~= nil then
opts = self.Jobs[current]
end
for i = 1, #opts do
if opts[i].id == jobId then
starter = opts[i].starter or ""
tier = opts[i].tier or 2
break
method('JobLabel', `if self.PlayerJob ~= "" and self.Jobs ~= nil then
for cls, list in pairs(self.Jobs) do
for i = 1, #list do
if list[i].id == self.PlayerJob then
return list[i].name
end
end
end
end
if self.SelectedClass == "warrior" then
return "전사"
elseif self.SelectedClass == "bandit" then
return "도적"
elseif self.SelectedClass == "magician" then
return "마법사"
end
return "플레이어"`, [], 0, 'string'),
method('SetJob', `self.PlayerJob = jobId
local starter = ""
local opts = self.Jobs[self.SelectedClass] or {}
for i = 1, #opts do
if opts[i].id == jobId then
starter = opts[i].starter
end
end
self.PlayerJob = jobId
if starter ~= "" then
table.insert(self.RunDeck, starter)
local sc = self.Cards[starter]
if sc ~= nil then
self:Toast(tostring(tier) .. "차 전직: " .. self:JobLabel() .. "! 신규 카드 - " .. sc.name)
self:Toast("2차 전직: " .. 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

@@ -1,21 +0,0 @@
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const layoutMethods = [
method('PositionMonsterSlot', `local monster = self.Monsters[slot]
if monster == nil or monster.entity == nil or not isvalid(monster.entity) then
return
end
local transform = monster.entity.TransformComponent
if transform == nil then
return
end
local worldPos = transform.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(worldPos.x, worldPos.y + ${HEAD_OFFSET_Y}))
local uipos = _UILogic:ScreenToUIPosition(screen)
local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(slot))
if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then
slotEntity.UITransformComponent.anchoredPosition = uipos
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
];

View File

@@ -1,34 +0,0 @@
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const navigationMethods = [
method('GoLobbyMap', `self.LobbyTpTries = 0
local eventId = 0
local function tryTeleport()
self.LobbyTpTries = self.LobbyTpTries + 1
local localPlayer = _UserService.LocalPlayer
if localPlayer ~= nil then
if localPlayer.CurrentMapName ~= "${LOBBY_MAP}" then
_TeleportService:TeleportToMapPosition(localPlayer, ${LOBBY_SPAWN}, "${LOBBY_MAP}")
end
_TimerService:ClearTimer(eventId)
elseif self.LobbyTpTries > 50 then
_TimerService:ClearTimer(eventId)
end
end
eventId = _TimerService:SetTimerRepeat(tryTeleport, 0.1)`),
method('TeleportToActMap', `local maps = { ${ACT_MAPS.map((mapName) => `"${mapName}"`).join(', ')} }
local target = maps[self.Floor]
if target == nil then
return
end
local localPlayer = _UserService.LocalPlayer
if localPlayer == nil then
return
end
if localPlayer.CurrentMapName == target then
return
end
_TeleportService:TeleportToMapPosition(localPlayer, Vector3(-6, 0.03, 0), target)`),
];

View File

@@ -1,18 +0,0 @@
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const npcMethods = [
method('OnLobbyNpcInteract', `if self.RunActive == true then
return
end
if id == "run" then
self:ShowCharacterSelect()
elseif id == "codex" then
self:ShowCodex()
elseif id == "shop" then
self:ShowSoulShop()
elseif id == "board" then
self:ShowBoard()
end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]),
];

View File

@@ -1,304 +1,307 @@
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const renderMethods = [
method('BuffsLabel', `local parts = {}
if str ~= nil and str > 0 then table.insert(parts, "힘+" .. tostring(str)) end
if weak ~= nil and weak > 0 then table.insert(parts, "약화" .. tostring(weak)) end
if vuln ~= nil and vuln > 0 then table.insert(parts, "취약" .. tostring(vuln)) end
if poison ~= nil and poison > 0 then table.insert(parts, "독" .. tostring(poison)) end
return table.concat(parts, " ")`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'str' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'weak' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'vuln' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' },
], 0, 'string'),
method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do
local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i)
local m = self.Monsters[i]
if m ~= nil and m.alive == true then
self:SetEntityEnabled(base, true)
self:SetText(base .. "/Name", m.name)
self:SetText(base .. "/Hp", string.format("%d", m.hp) .. "/" .. string.format("%d", m.maxHp))
local intent = m.intents[m.intentIdx]
local t = ""
if intent ~= nil then
if intent.kind == "Attack" then
local atk = intent.value + m.str
if m.weak > 0 then atk = math.floor(atk * 0.75) end
if self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end
t = "공격 " .. tostring(atk)
elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value)
elseif intent.kind == "Debuff" then
if intent.effect == "weak" then t = "약화 " .. tostring(intent.value) .. " 부여"
else t = "취약 " .. tostring(intent.value) .. " 부여" end
elseif intent.kind == "AddCard" then
t = "저주 카드 추가"
end
end
self:SetText(base .. "/Intent", t)
local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0
local shownTarget = self.TargetIndex
if dragActive == true then shownTarget = self.DragTargetIndex end
self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive)
self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive)
local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent")
if intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then
if intent.kind == "Attack" then
intentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)
elseif intent.kind == "Debuff" then
intentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 1)
elseif intent.kind == "AddCard" then
intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 1)
else
intentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)
end
end
self:SetHpBar(base .. "/HpBarFill", m.hp, m.maxHp, ${HP_BAR_W})
self:SetEntityEnabled(base .. "/BlockBadge", m.block > 0)
self:SetText(base .. "/BlockBadge/Value", string.format("%d", m.block))
self:SetText(base .. "/Buffs", self:BuffsLabel(m.str, m.weak, m.vuln, m.poison or 0))
else
self:SetEntityEnabled(base, false)
end
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp))
self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "불가침" .. tostring(self.PlayerIntangible)
end
if self.PlayerDex ~= nil and self.PlayerDex > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "민첩+" .. tostring(self.PlayerDex)
end
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())
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const renderMethods = [
method('BuffsLabel', `local parts = {}
if str ~= nil and str > 0 then table.insert(parts, "힘+" .. tostring(str)) end
if weak ~= nil and weak > 0 then table.insert(parts, "약화" .. tostring(weak)) end
if vuln ~= nil and vuln > 0 then table.insert(parts, "취약" .. tostring(vuln)) end
if poison ~= nil and poison > 0 then table.insert(parts, "독" .. tostring(poison)) end
return table.concat(parts, " ")`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'str' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'weak' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'vuln' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' },
], 0, 'string'),
method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do
local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i)
local m = self.Monsters[i]
if m ~= nil and m.alive == true then
self:SetEntityEnabled(base, true)
self:SetText(base .. "/Name", m.name)
self:SetText(base .. "/Hp", string.format("%d", m.hp) .. "/" .. string.format("%d", m.maxHp))
local intent = m.intents[m.intentIdx]
local t = ""
if intent ~= nil then
if intent.kind == "Attack" then
local atk = intent.value + m.str
if m.weak > 0 then atk = math.floor(atk * 0.75) end
if self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end
t = "공격 " .. tostring(atk)
elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value)
elseif intent.kind == "Debuff" then
if intent.effect == "weak" then t = "약화 " .. tostring(intent.value) .. " 부여"
else t = "취약 " .. tostring(intent.value) .. " 부여" end
elseif intent.kind == "AddCard" then
t = "저주 카드 추가"
end
end
self:SetText(base .. "/Intent", t)
local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0
local shownTarget = self.TargetIndex
if dragActive == true then shownTarget = self.DragTargetIndex end
self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive)
self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive)
local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent")
if intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then
if intent.kind == "Attack" then
intentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)
elseif intent.kind == "Debuff" then
intentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 1)
elseif intent.kind == "AddCard" then
intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 1)
else
intentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)
end
end
self:SetHpBar(base .. "/HpBarFill", m.hp, m.maxHp, ${HP_BAR_W})
self:SetEntityEnabled(base .. "/BlockBadge", m.block > 0)
self:SetText(base .. "/BlockBadge/Value", string.format("%d", m.block))
self:SetText(base .. "/Buffs", self:BuffsLabel(m.str, m.weak, m.vuln, m.poison or 0))
else
self:SetEntityEnabled(base, false)
end
end
if self.HolyChargeCount ~= nil and self.HolyChargeCount > 0 then
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp))
self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
if self.PlayerDex ~= nil and self.PlayerDex > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "홀리 차지 " .. tostring(self.HolyChargeCount) .. "/" .. tostring(self:GetHolyChargeMax())
pb = pb .. "민첩+" .. tostring(self.PlayerDex)
end
if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "가시" .. tostring(self.PlayerThorns)
end
if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then
local names = {}
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil then table.insert(names, pc.name) end
end
if pb ~= "" then pb = pb .. " · " end
pb = pb .. table.concat(names, " ")
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb)
self:RenderRun()`),
method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0))
local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey
local pop = _EntityService:GetEntityByPath(base)
if pop == nil then
return
end
self.DmgPopSeq = (self.DmgPopSeq or 0) + 1
local popSeq = self.DmgPopSeq
self:SetText(base, "")
local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} }
local shown = tostring(math.max(0, math.floor(amount)))
if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then
shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS})
end
local digits = {}
for i = 1, string.len(shown) do
table.insert(digits, tonumber(string.sub(shown, i, i)) or 0)
end
local totalW = #digits * ${DAMAGE_POP_DIGIT_W} + math.max(0, #digits - 1) * ${DAMAGE_POP_DIGIT_SPACING}
local startX = -totalW / 2 + ${DAMAGE_POP_DIGIT_W} / 2
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false)
end
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
local digitPath = base .. "/Digit" .. tostring(i)
local digitEntity = _EntityService:GetEntityByPath(digitPath)
if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then
if digits[i] ~= nil then
digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1]
digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
if digitEntity.UITransformComponent ~= nil then
digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0)
end
self:SetEntityEnabled(digitPath, true)
else
self:SetEntityEnabled(digitPath, false)
end
end
end
local popPos = nil
local m = self.Monsters[slot]
if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then
local wp = m.entity.TransformComponent.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45}))
popPos = _UILogic:ScreenToUIPosition(screen)
else
local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey)
if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then
local sp = slotEntity.UITransformComponent.anchoredPosition
popPos = Vector2(sp.x, sp.y + 76)
end
end
if pop ~= nil and pop.UITransformComponent ~= nil then
if popPos ~= nil then
pop.UITransformComponent.anchoredPosition = popPos
else
pop.UITransformComponent.anchoredPosition = Vector2(0, 120)
end
end
self:SetEntityEnabled(base, true)
for i = 1, 6 do
_TimerService:SetTimerOnce(function()
if self.DmgPopSeq ~= popSeq then
return
end
local p = _EntityService:GetEntityByPath(base)
if p ~= nil and p.UITransformComponent ~= nil then
local cur = p.UITransformComponent.anchoredPosition
p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7)
end
end, 0.045 * i)
end
_TimerService:SetTimerOnce(function()
if self.DmgPopSeq ~= popSeq then
return
end
self:SetEntityEnabled(base, false)
end, 0.48)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
]),
method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/CombatHud/PlayerPanel/DmgPop"
if amount > 0 then
self:SetText(base, "-" .. string.format("%d", amount))
else
self:SetText(base, "막음")
end
self:SetEntityEnabled(base, true)
_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
method('PlayerAttackMotion', `local lp = _UserService.LocalPlayer
if lp == nil then
return
end
if lp.StateComponent == nil then
return
end
pcall(function() lp.StateComponent:ChangeState("ATTACK") end)
_TimerService:SetTimerOnce(function()
if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then
pcall(function() lp.StateComponent:ChangeState("IDLE") end)
end
end, 0.5)`),
method('PlayerHitMotion', `local lp = _UserService.LocalPlayer
if lp == nil then
return
end
if lp.StateComponent ~= nil then
pcall(function() lp.StateComponent:ChangeState("HIT") end)
end
local tr = lp.TransformComponent
if tr == nil then
return
end
local p = tr.Position
tr.Position = Vector3(p.x - 0.15, p.y, p.z)
_TimerService:SetTimerOnce(function()
if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then
lp.TransformComponent.Position = Vector3(p.x, p.y, p.z)
end
end, 0.15)`),
method('MonsterLunge', `local m = self.Monsters[idx]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return
end
if m.motionBusy == true then
return
end
m.motionBusy = true
local e = m.entity
local tr = e.TransformComponent
if tr == nil then
m.motionBusy = false
return
end
local p = tr.Position
tr.Position = Vector3(p.x - 0.35, p.y, p.z)
_TimerService:SetTimerOnce(function()
if isvalid(e) and e.TransformComponent ~= nil then
e.TransformComponent.Position = Vector3(p.x, p.y, p.z)
end
m.motionBusy = false
end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]),
method('MonsterHitMotion', `local m = self.Monsters[slot]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return
end
local e = m.entity
if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then
e.SpriteRendererComponent.SpriteRUID = m.hitClip
_TimerService:SetTimerOnce(function()
if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then
e.SpriteRendererComponent.SpriteRUID = m.standClip
end
end, 0.5)
else
if m.motionBusy == true then
return
end
m.motionBusy = true
local tr = e.TransformComponent
if tr == nil then
m.motionBusy = false
return
end
local p = tr.Position
local seq = { 0.12, -0.12, 0 }
for i = 1, #seq do
local dx = seq[i]
_TimerService:SetTimerOnce(function()
if isvalid(e) and e.TransformComponent ~= nil then
e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z)
end
if i == #seq then
m.motionBusy = false
end
end, 0.06 * i)
end
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('SetHpBar', `local e = _EntityService:GetEntityByPath(path)
if e == nil or e.UITransformComponent == nil then
return
end
local ratio = 0
if maxHp > 0 then ratio = hp / maxHp end
if ratio < 0 then ratio = 0 end
local w = width * ratio
e.UITransformComponent.RectSize = Vector2(w, 14)`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'width' },
]),
method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then
self.TargetIndex = slot
self:RenderCombat()
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('RenderRun', `local floorText = "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층"
if self.AscensionLevel > 0 then
floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel)
end
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Floor", floorText)
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
local names = {}
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil then table.insert(names, pc.name) end
end
if pb ~= "" then pb = pb .. " · " end
pb = pb .. table.concat(names, " ")
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb)
self:RenderRun()`),
method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0))
local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey
local pop = _EntityService:GetEntityByPath(base)
if pop == nil then
return
end
self.DmgPopSeq = (self.DmgPopSeq or 0) + 1
local popSeq = self.DmgPopSeq
self:SetText(base, "")
local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} }
local shown = tostring(math.max(0, math.floor(amount)))
if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then
shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS})
end
local digits = {}
for i = 1, string.len(shown) do
table.insert(digits, tonumber(string.sub(shown, i, i)) or 0)
end
local totalW = #digits * ${DAMAGE_POP_DIGIT_W} + math.max(0, #digits - 1) * ${DAMAGE_POP_DIGIT_SPACING}
local startX = -totalW / 2 + ${DAMAGE_POP_DIGIT_W} / 2
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false)
end
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
local digitPath = base .. "/Digit" .. tostring(i)
local digitEntity = _EntityService:GetEntityByPath(digitPath)
if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then
if digits[i] ~= nil then
digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1]
digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
if digitEntity.UITransformComponent ~= nil then
digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0)
end
self:SetEntityEnabled(digitPath, true)
else
self:SetEntityEnabled(digitPath, false)
end
end
end
local popPos = nil
local m = self.Monsters[slot]
if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then
local wp = m.entity.TransformComponent.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45}))
popPos = _UILogic:ScreenToUIPosition(screen)
else
local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey)
if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then
local sp = slotEntity.UITransformComponent.anchoredPosition
popPos = Vector2(sp.x, sp.y + 76)
end
end
if pop ~= nil and pop.UITransformComponent ~= nil then
if popPos ~= nil then
pop.UITransformComponent.anchoredPosition = popPos
else
pop.UITransformComponent.anchoredPosition = Vector2(0, 120)
end
end
self:SetEntityEnabled(base, true)
for i = 1, 6 do
_TimerService:SetTimerOnce(function()
if self.DmgPopSeq ~= popSeq then
return
end
local p = _EntityService:GetEntityByPath(base)
if p ~= nil and p.UITransformComponent ~= nil then
local cur = p.UITransformComponent.anchoredPosition
p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7)
end
end, 0.045 * i)
end
_TimerService:SetTimerOnce(function()
if self.DmgPopSeq ~= popSeq then
return
end
self:SetEntityEnabled(base, false)
end, 0.48)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
]),
method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/CombatHud/PlayerPanel/DmgPop"
if amount > 0 then
self:SetText(base, "-" .. string.format("%d", amount))
else
self:SetText(base, "막음")
end
self:SetEntityEnabled(base, true)
_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
method('PlayerAttackMotion', `local lp = _UserService.LocalPlayer
if lp == nil then
return
end
if lp.StateComponent == nil then
return
end
pcall(function() lp.StateComponent:ChangeState("ATTACK") end)
_TimerService:SetTimerOnce(function()
if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then
pcall(function() lp.StateComponent:ChangeState("IDLE") end)
end
end, 0.5)`),
method('PlayerHitMotion', `local lp = _UserService.LocalPlayer
if lp == nil then
return
end
if lp.StateComponent ~= nil then
pcall(function() lp.StateComponent:ChangeState("HIT") end)
end
local tr = lp.TransformComponent
if tr == nil then
return
end
local p = tr.Position
tr.Position = Vector3(p.x - 0.15, p.y, p.z)
_TimerService:SetTimerOnce(function()
if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then
lp.TransformComponent.Position = Vector3(p.x, p.y, p.z)
end
end, 0.15)`),
method('MonsterLunge', `local m = self.Monsters[idx]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return
end
if m.motionBusy == true then
return
end
m.motionBusy = true
local e = m.entity
local tr = e.TransformComponent
if tr == nil then
m.motionBusy = false
return
end
local p = tr.Position
tr.Position = Vector3(p.x - 0.35, p.y, p.z)
_TimerService:SetTimerOnce(function()
if isvalid(e) and e.TransformComponent ~= nil then
e.TransformComponent.Position = Vector3(p.x, p.y, p.z)
end
m.motionBusy = false
end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]),
method('MonsterHitMotion', `local m = self.Monsters[slot]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return
end
local e = m.entity
if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then
e.SpriteRendererComponent.SpriteRUID = m.hitClip
_TimerService:SetTimerOnce(function()
if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then
e.SpriteRendererComponent.SpriteRUID = m.standClip
end
end, 0.5)
else
if m.motionBusy == true then
return
end
m.motionBusy = true
local tr = e.TransformComponent
if tr == nil then
m.motionBusy = false
return
end
local p = tr.Position
local seq = { 0.12, -0.12, 0 }
for i = 1, #seq do
local dx = seq[i]
_TimerService:SetTimerOnce(function()
if isvalid(e) and e.TransformComponent ~= nil then
e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z)
end
if i == #seq then
m.motionBusy = false
end
end, 0.06 * i)
end
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('SetHpBar', `local e = _EntityService:GetEntityByPath(path)
if e == nil or e.UITransformComponent == nil then
return
end
local ratio = 0
if maxHp > 0 then ratio = hp / maxHp end
if ratio < 0 then ratio = 0 end
local w = width * ratio
e.UITransformComponent.RectSize = Vector2(w, 14)`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'width' },
]),
method('PositionMonsterSlot', `local m = self.Monsters[slot]
if m == nil or m.entity == nil or not isvalid(m.entity) then
return
end
local tr = m.entity.TransformComponent
if tr == nil then
return
end
local wp = tr.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y}))
local uipos = _UILogic:ScreenToUIPosition(screen)
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(slot))
if e ~= nil and e.UITransformComponent ~= nil then
e.UITransformComponent.anchoredPosition = uipos
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then
self.TargetIndex = slot
self:RenderCombat()
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('RenderRun', `local floorText = "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층"
if self.AscensionLevel > 0 then
floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel)
end
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Floor", floorText)
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
];

View File

@@ -5,7 +5,7 @@ import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER,
export const rewardMethods = [
method('CardPool', `local pool = {}
for id, c in pairs(self.Cards) do
if c.token ~= true and self:CanUseClassCard(c.class) == true then
if c.token ~= true and (c.class == self.SelectedClass or (self.PlayerJob ~= "" and c.class == self.PlayerJob)) then
table.insert(pool, id)
end
end
@@ -38,7 +38,7 @@ end`),
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
]),
method('PickReward', `if self.CombatOver ~= true or self.RunActive ~= true then
method('PickReward', `if self.CombatOver ~= true or self.RunActive ~= true then
return
end
if slot ~= 0 and self.RewardChoices ~= nil then
@@ -47,11 +47,6 @@ if slot ~= 0 and self.RewardChoices ~= nil then
table.insert(self.RunDeck, id)
end
end
if self.BonusRewardScreens ~= nil and self.BonusRewardScreens > 0 and slot ~= 0 then
self.BonusRewardScreens = self.BonusRewardScreens - 1
self:OfferReward()
return
end
local hud = _EntityService:GetEntityByPath("/ui/RunUIGroup/RewardHud")
if hud ~= nil then
hud.Enable = false

View File

@@ -1,14 +1,14 @@
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 { 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 { CARDS, ENEMIES, CLASSES, JOBS, 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, 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';
export const runMethods = [
method('StartRun', `if self.SelectedClass == "magician" then
self.PlayerMaxHp = ${CLASSES.magician.maxHp}
self.RunDeck = { ${CARDS.starterDecks.magician.map(luaStr).join(', ')} }
elseif self.SelectedClass == "rogue" then
self.PlayerMaxHp = ${CLASSES.rogue.maxHp}
self.RunDeck = { ${CARDS.starterDecks.rogue.map(luaStr).join(', ')} }
elseif self.SelectedClass == "bandit" then
self.PlayerMaxHp = ${CLASSES.bandit.maxHp}
self.RunDeck = { ${CARDS.starterDecks.bandit.map(luaStr).join(', ')} }
else
self.PlayerMaxHp = ${CLASSES.warrior.maxHp}
self.RunDeck = { ${CARDS.starterDecks.warrior.map(luaStr).join(', ')} }
@@ -30,9 +30,6 @@ self.CurrentNodeId = ""
self.CurrentEnemyId = ""
self.PlayerJob = ""
${luaJobsTable(JOBS)}
${luaJobMetaTable(JOB_META)}
${luaClassGroupsTable(CLASS_GROUPS)}
${luaClassLineagesTable(CLASS_LINEAGES)}
${luaFramesTable()}
${luaNodeIconsTable()}
${luaCharsTable()}
@@ -70,41 +67,13 @@ self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
self.MaxEnergy = 3
self.Turn = 0
self.PlayerBlock = 0
self.BlockGainMultiplier = 1
self.CardsDrawnThisCombat = 0
self.HandCostZeroThisTurn = false
self.DrawDisabledThisTurn = false
self.NextSkillCostZero = false
self.NextSkillRepeatCount = 0
self.SkillCostReductionThisTurn = 0
self.CombatCardCostReduction = {}
self.SkillSlyOnPlayCards = {}
self.TurnSkillSlyCards = {}
self.ShivFirstDamageBonusUsed = false
self.ActiveAttackDamageVsWeakMultiplier = 1
self.DrawDamageThisTurn = 0
self.DrawPoisonThisTurn = 0
self.ShivAoeThisCombat = false
self.PoisonApplicationsThisCombat = 0
self.EnemyStrengthLossThisTurn = 0
self.PlayerStr = 0
self.PlayerDex = 0
self.PlayerThorns = 0
self.PlayerWeak = 0
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
self.ClayBlockNext = 0
@@ -113,16 +82,6 @@ self.DiscardSelectTotal = 0
self.DiscardPostShiv = 0
self.DiscardShivPerPick = 0
self.RetainSelectActive = false
self.ReserveSelectActive = false
self.NextTurnBlock = 0
self.NextTurnDraw = 0
self.NextTurnKeepBlock = false
self.NextTurnAttackMultiplier = 1
self.TurnAttackMultiplier = 1
self.NextTurnSelectPrompt = ""
self.NextTurnSelectCopies = 0
self.NextTurnAddCards = {}
self.ZeroCostCardIdsThisTurn = {}
self.CombatOver = false
self.DiscardPile = {}
self.ExhaustPile = {}
@@ -133,7 +92,6 @@ for i = 1, #self.RunDeck do
self.DrawPile[i] = self.RunDeck[i]
end
self:Shuffle(self.DrawPile)
self:PrepareCombatDrawPile()
self:BuildMonsters()
self:RenderCombat()
self:StartPlayerTurn()
@@ -216,8 +174,7 @@ 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 actMultipliers = { ${ACT_DIFFICULTY_MULTIPLIERS.join(', ')} }
local mult = actMultipliers[self.Floor] or actMultipliers[#actMultipliers]
local mult = 1 + (self.Floor - 1) * 0.45
if g == "elite" or g == "boss" then
mult = mult + self:AscEliteBonus()
end
@@ -249,7 +206,7 @@ for i = 1, n do
local startIdx = 1
if #intents > 0 then startIdx = math.random(1, #intents) end
self.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,
hp = maxHp, maxHp = maxHp, block = 0, str = e.str or 0, weak = 0, vuln = 0, poison = 0, artifact = e.artifact or 0,
hp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, poison = 0,
hitClip = hitClip, standClip = standClip, motionBusy = false,
intents = intents, intentIdx = startIdx, alive = true, slot = i }
self:ReviveMonsterEntity(item.entity)

View File

@@ -1,24 +1,37 @@
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const runEndMethods = [
method('ShowResult', `self:SetText("/ui/RunUIGroup/CombatHud/Result", text)
local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/Result")
if entity ~= nil then
entity.Enable = true
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
method('EndRun', `local msg = text
if text == "런 클리어!" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then
self.AscensionUnlocked = self.AscensionUnlocked + 1
local lp = _UserService.LocalPlayer
if lp ~= nil then
self:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId)
end
self:RenderAscension()
msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!"
end
self:ShowResult(msg)
self.RunActive = false
_TimerService:SetTimerOnce(function() self:ShowLobby() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
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 { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const runEndMethods = [
method('TeleportToActMap', `local maps = { ${ACT_MAPS.map((m) => `"${m}"`).join(', ')} }
local target = maps[self.Floor]
if target == nil then
return
end
local lp = _UserService.LocalPlayer
if lp == nil then
return
end
if lp.CurrentMapName == target then
return
end
_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)`),
method('ShowResult', `self:SetText("/ui/RunUIGroup/CombatHud/Result", text)
local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/Result")
if entity ~= nil then
entity.Enable = true
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
method('EndRun', `local msg = text
if text == "런 클리어!" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then
self.AscensionUnlocked = self.AscensionUnlocked + 1
local lp = _UserService.LocalPlayer
if lp ~= nil then
self:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId)
end
self:RenderAscension()
msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!"
end
self:ShowResult(msg)
self.RunActive = false
_TimerService:SetTimerOnce(function() self:ShowLobby() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
];

View File

@@ -2,7 +2,7 @@ import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, A
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, 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';
export const screensMethods = [
export const stateMethods = [
method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false)
self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false)
self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false)
@@ -21,14 +21,14 @@ self:SetEntityEnabled("/ui/DeckUIGroup/DeckAllHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`),
method('ActivateUIGroups', `local function enableGroup(name)
local group = _EntityService:GetEntityByPath("/ui/" .. name)
if group ~= nil then group:SetEnable(true) end
method('ActivateUIGroups', `local function grp(n)
local g = _EntityService:GetEntityByPath("/ui/" .. n)
if g ~= nil then g:SetEnable(true) end
end
enableGroup("SelectUIGroup")
enableGroup("LobbyUIGroup")
enableGroup("RunUIGroup")
enableGroup("DeckUIGroup")`, [], 2),
grp("SelectUIGroup")
grp("LobbyUIGroup")
grp("RunUIGroup")
grp("DeckUIGroup")`, [], 2),
method('ShowState', `self:HideGameHud()
self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu")
self:SetEntityEnabled("/ui/SelectUIGroup/CharacterSelectHud", state == "charselect")
@@ -75,7 +75,7 @@ if thief ~= nil and (thief.ButtonComponent ~= nil or thief:AddComponent("ButtonC
thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler)
self.ThiefSelectHandler = nil
end
self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("rogue") end)
self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end)
end
local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton")
if mage ~= nil and (mage.ButtonComponent ~= nil or mage:AddComponent("ButtonComponent") ~= nil) then
@@ -135,17 +135,44 @@ self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)
self:BindLobbyButtons()
self:BindMenuButtons()
self:GoLobbyMap()`),
method('RenderSoulLabel', `local soulPoints = self.SoulPoints or 0
self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", soulPoints))
self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", soulPoints))`),
method('GoLobbyMap', `self.LobbyTpTries = 0
local eventId = 0
local function go()
self.LobbyTpTries = self.LobbyTpTries + 1
local lp = _UserService.LocalPlayer
if lp ~= nil then
if lp.CurrentMapName ~= "${LOBBY_MAP}" then
_TeleportService:TeleportToMapPosition(lp, ${LOBBY_SPAWN}, "${LOBBY_MAP}")
end
_TimerService:ClearTimer(eventId)
elseif self.LobbyTpTries > 50 then
_TimerService:ClearTimer(eventId)
end
end
eventId = _TimerService:SetTimerRepeat(go, 0.1)`),
method('OnLobbyNpcInteract', `if self.RunActive == true then
return
end
if id == "run" then
self:ShowCharacterSelect()
elseif id == "codex" then
self:ShowCodex()
elseif id == "shop" then
self:ShowSoulShop()
elseif id == "board" then
self:ShowBoard()
end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]),
method('RenderSoulLabel', `local s = self.SoulPoints or 0
self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s))
self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`),
method('BindLobbyButtons', `if self.LobbyBound == true then
return
end
self.LobbyBound = true
local function bindClick(path, handler)
local entity = _EntityService:GetEntityByPath(path)
if entity ~= nil and (entity.ButtonComponent ~= nil or entity:AddComponent("ButtonComponent") ~= nil) then
entity:ConnectEvent(ButtonClickEvent, handler)
local function bindClick(path, fn)
local e = _EntityService:GetEntityByPath(path)
if e ~= nil and (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then
e:ConnectEvent(ButtonClickEvent, fn)
end
end
bindClick("/ui/LobbyUIGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end)

View File

@@ -3,47 +3,6 @@ import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_
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';
export const tooltipMethods = [
method('FormatCardDescription', `if desc == nil or desc == "" then
return ""
end
local function replacePlain(text, needle, replacement)
local out = ""
local pos = 1
while true do
local s, e = string.find(text, needle, pos, true)
if s == nil then
out = out .. string.sub(text, pos)
break
end
out = out .. string.sub(text, pos, s - 1) .. replacement
pos = e + 1
end
return out
end
local terms = {
"교활",
"보존",
"민첩",
"가시",
"소멸",
"선천성",
"취약",
"약화",
"독",
"광역",
"관통",
"방어도",
"힘",
"스킬",
"공격",
"파워",
}
local out = desc
for i = 1, #terms do
local term = terms[i]
out = replacePlain(out, term, "<color=#70D6FF>" .. term .. "</color>")
end
return out`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'desc' }], 0, 'string'),
method('BuildCardKeywordTooltip', `if c == nil then
return ""
end
@@ -105,9 +64,6 @@ return out`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [
method('HoverCard', `if self.DragSlot ~= nil and self.DragSlot > 0 then
return
end
if self.Hand == nil then
return
end
local cardId = self.Hand[slot]
if cardId == nil then
return

View File

@@ -3,10 +3,7 @@
import { POTIONS } from './lib/data.mjs';
import { prop, codeblock, RUN_LENGTH } from './lib/codeblock.mjs';
import { bootMethods } from './cb/boot.mjs';
import { screensMethods } from './cb/screens.mjs';
import { npcMethods } from './cb/npc.mjs';
import { navigationMethods } from './cb/navigation.mjs';
import { layoutMethods } from './cb/layout.mjs';
import { stateMethods } from './cb/state.mjs';
import { soulMethods } from './cb/soul.mjs';
import { charSelectMethods } from './cb/charselect.mjs';
import { runMethods } from './cb/run.mjs';
@@ -48,9 +45,6 @@ function writeCodeblocks() {
prop('any', 'AscPlusHandler'),
prop('any', 'JobOpts'),
prop('any', 'Jobs'),
prop('any', 'JobMeta'),
prop('any', 'ClassGroups'),
prop('any', 'ClassLineages'),
prop('number', 'AscensionLevel', '0'),
prop('number', 'AscensionUnlocked', '0'),
prop('any', 'StartGameHandler'),
@@ -64,7 +58,6 @@ function writeCodeblocks() {
prop('any', 'AllDeckCloseHandler'),
prop('number', 'SoulPoints', '0'),
prop('boolean', 'LobbyBound', 'false'),
prop('boolean', 'ButtonsBound', 'false'),
prop('number', 'LobbyTpTries', '0'),
prop('boolean', 'CodexMode', 'false'),
prop('any', 'CodexCards'),
@@ -88,7 +81,6 @@ function writeCodeblocks() {
prop('number', 'PlayerHp', '0'),
prop('number', 'PlayerMaxHp', '80'),
prop('number', 'PlayerBlock', '0'),
prop('number', 'BlockGainMultiplier', '1'),
prop('number', 'PlayerDex', '0'),
prop('number', 'PlayerThorns', '0'),
prop('boolean', 'CombatOver', 'false'),
@@ -120,38 +112,13 @@ function writeCodeblocks() {
prop('number', 'PlayerStr', '0'),
prop('number', 'PlayerWeak', '0'),
prop('number', 'PlayerVuln', '0'),
prop('number', 'PlayerIntangible', '0'),
prop('any', 'PlayerPowers'),
prop('any', 'Potions'),
prop('any', 'RunPotions'),
prop('number', 'PotionSlots', String(POTIONS.baseSlots)),
prop('string', 'ShopPotion', '""'),
prop('boolean', 'ShopPotionBought', 'false'),
prop('number', 'CardsDrawnThisCombat', '0'),
prop('boolean', 'HandCostZeroThisTurn', 'false'),
prop('boolean', 'DrawDisabledThisTurn', 'false'),
prop('number', 'SkillCostReductionThisTurn', '0'),
prop('any', 'SkillSlyOnPlayCards'),
prop('any', 'TurnSkillSlyCards'),
prop('boolean', 'ShivFirstDamageBonusUsed', 'false'),
prop('any', 'CombatCardCostReduction'),
prop('number', 'ActiveAttackDamageVsWeakMultiplier', '1'),
prop('number', 'DrawDamageThisTurn', '0'),
prop('number', 'DrawPoisonThisTurn', '0'),
prop('boolean', 'ShivAoeThisCombat', 'false'),
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'),
prop('number', 'ClayBlockNext', '0'),
prop('number', 'PotionMenuSlot', '0'),
@@ -163,26 +130,10 @@ function writeCodeblocks() {
prop('number', 'DiscardSelectTotal', '0'),
prop('number', 'DiscardPostShiv', '0'),
prop('number', 'DiscardShivPerPick', '0'),
prop('number', 'DiscardPostDraw', '0'),
prop('number', 'DiscardDrawPerPick', '0'),
prop('boolean', 'RetainSelectActive', 'false'),
prop('boolean', 'ReserveSelectActive', 'false'),
prop('number', 'NextTurnBlock', '0'),
prop('number', 'NextTurnDraw', '0'),
prop('boolean', 'NextTurnKeepBlock', 'false'),
prop('number', 'NextTurnAttackMultiplier', '1'),
prop('number', 'TurnAttackMultiplier', '1'),
prop('string', 'NextTurnSelectPrompt', '""'),
prop('number', 'NextTurnSelectCopies', '0'),
prop('boolean', 'NextSkillCostZero', 'false'),
prop('number', 'NextSkillRepeatCount', '0'),
prop('any', 'NextTurnAddCards'),
prop('any', 'ZeroCostCardIdsThisTurn'),
], [
...bootMethods,
...screensMethods,
...npcMethods,
...navigationMethods,
...stateMethods,
...soulMethods,
...charSelectMethods,
...runMethods,
@@ -193,7 +144,6 @@ function writeCodeblocks() {
...jobMethods,
...runEndMethods,
...renderMethods,
...layoutMethods,
...rewardMethods,
...itemMethods,
...tooltipMethods,

View File

@@ -54,8 +54,7 @@ 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, ACT_DIFFICULTY_MULTIPLIERS, LOBBY_MAP, LOBBY_SPAWN };
export { prop, method, codeblock, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN };

View File

@@ -6,7 +6,7 @@ const ENEMIES = JSON.parse(readFileSync('data/enemies.json', 'utf8'));
// 검증 (fail-fast): 잘못된 데이터면 생성 중단
const CLASSES = {
warrior: { label: '전사', maxHp: 80 },
rogue: { label: '도적', maxHp: 70 },
bandit: { label: '도적', maxHp: 70 },
magician: { label: '마법사', maxHp: 70 },
};
for (const cls of Object.keys(CLASSES)) {
@@ -15,83 +15,29 @@ for (const cls of Object.keys(CLASSES)) {
if (!CARDS.cards[id]) throw new Error(`[gen-slaydeck] starterDecks.${cls}에 없는 카드 id 참조: ${id}`);
}
}
// 전직 옵션
// 전직 옵션 (클래스별 2차 — JobSelectHud 동적 구성·SetJob 대표 카드)
const JOBS = {
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' },
{ id: 'fighter', name: '파이터', desc: '공격 특화\n콤보 어택 · 버서크\n라이징 어택', starter: 'ComboAttack' },
{ id: 'page', name: '페이지', desc: '속성 차지 특화\n썬더/블리자드 차지\n파워 가드', starter: 'ThunderCharge' },
{ id: 'spearman', name: '스피어맨', desc: '방어·관통 특화\n피어스 · 아이언 월\n하이퍼 바디', starter: 'Pierce' },
],
magician: [
{ id: 'firepoison', name: '위자드(불·독)', desc: '화염·독 특화\n파이어 애로우\n포이즌 브레스 · 앰플', starter: 'FireArrow', tier: 2, parent: 'magician' },
{ id: 'icelightning', name: '위자드(썬·콜)', desc: '광역·빙결 특화\n썬더 볼트(전체)\n콜드 빔 · 칠링 스텝', starter: 'ThunderBolt', tier: 2, parent: 'magician' },
{ id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal', tier: 2, parent: 'magician' },
{ id: 'firepoison', name: '위자드(불·독)', desc: '화염·독 특화\n파이어 애로우\n포이즌 브레스 · 앰플', starter: 'FireArrow' },
{ id: 'icelightning', name: '위자드(썬·콜)', desc: '광역·빙결 특화\n썬더 볼트(전체)\n콜드 빔 · 칠링 스텝', starter: 'ThunderBolt' },
{ id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal' },
],
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: 'SpiritJavelin', tier: 3, parent: 'assassin' },
],
thief: [
{ id: 'thiefmaster', name: 'Thief Master', desc: 'Thief의 3차 전직\n단검·교활·중독 심화\n연계 운영 완성', starter: 'Venom', tier: 3, parent: 'thief' },
bandit: [
{ id: 'shiv', name: 'Shiv', desc: 'Many small attacks\nBlade Dance\nAccuracy · After Image', starter: 'BladeDance' },
{ id: 'poisoner', name: 'Poison', desc: 'Poison scaling\nDeadly Poison\nCatalyst · Noxious Fumes', starter: 'DeadlyPoison' },
{ id: 'trickster', name: 'Trickster', desc: 'Draw and tempo\nAcrobatics\nAdrenaline · Tools', starter: 'Acrobatics' },
],
};
for (const [cls, jobs] of Object.entries(JOBS)) {
for (const j of jobs) {
if (j.starter && !CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
if (!CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
}
}
const CLASS_GROUPS = {
warrior: ['warrior', 'fighter', 'crusader', 'page', 'knight', 'spearman', 'berserker'],
magician: ['magician', 'firepoison', 'icelightning', 'cleric'],
rogue: ['rogue', 'assassin', 'hermit', 'thief', 'thiefmaster'],
};
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'],
cleric: ['magician', 'cleric'],
rogue: ['rogue'],
assassin: ['rogue', 'assassin'],
hermit: ['rogue', 'assassin', 'hermit'],
thief: ['rogue', 'thief'],
thiefmaster: ['rogue', 'thief', 'thiefmaster'],
};
const JOB_META = {};
for (const [sourceClass, jobs] of Object.entries(JOBS)) {
for (const job of jobs) {
JOB_META[job.id] = {
name: job.name,
starter: job.starter,
tier: job.tier ?? 2,
parent: job.parent ?? sourceClass,
sourceClass,
};
}
}
// 영혼(soul) 메타 해금 — 2차 전직 후 보스 클리어로 영혼 적립, 로비 영혼상점에서 구매 → 다음 런 이점
const SOUL_UNLOCKS = [
{ key: 'meso', name: '두둑한 지갑', desc: '런 시작 시 메소 +60', cost: 3 },
@@ -139,23 +85,27 @@ function luaCharsTable() {
}
// 맵은 런타임 절차 생성(GenerateMap Lua ↔ tools/map/rogue-map.mjs 미러). 정적 data/map.json 제거됨.
const MAP_ROWS = 6;
const MAP_ROWS = 6; // 걷는 행 1..6, 보스 row 7 (depth 최대 7)
const MAP_COLS = 4;
// 보물 상자 스프라이트 (공식 maplestory 리소스, 메이커 선별)
const CHEST_CLOSED_RUID = '43df67920c0d43298e0d93c02c6afa71';
const CHEST_OPEN_RUID = '09c5cee56fd640bf8ae3a18ce50f4759';
// 노드 맵 아이콘/배경 (공식 maplestory RUID, data/nodeicons.json 단일 소스 — 교체 시 이 파일만 수정 후 재생성)
const NODEICONS = JSON.parse(readFileSync('data/nodeicons.json', 'utf8'));
for (const t of ['combat', 'elite', 'boss', 'shop', 'rest', 'treasure']) {
if (!/^[0-9a-f]{32}$/.test((NODEICONS.icons || {})[t] || '')) throw new Error(`[gen-slaydeck] nodeicons.json icons.${t} RUID 누락/형식오류`);
}
if (!/^[0-9a-f]{32}$/.test(NODEICONS.background || '')) throw new Error('[gen-slaydeck] nodeicons.json background RUID 누락/형식오류');
// 캐릭터 선택 초상화 (메이커 임포트 RUID, data/characters.json 단일 소스 — 교체 시 이 파일만 수정 후 재생성)
const CHARS = JSON.parse(readFileSync('data/characters.json', 'utf8'));
for (const c of ['warrior', 'magician', 'rogue']) {
for (const c of ['warrior', 'magician', 'bandit']) {
if (!/^[0-9a-f]{32}$/.test((CHARS.portraits || {})[c] || '')) throw new Error(`[gen-slaydeck] characters.json portraits.${c} RUID 누락/형식오류`);
}
// 전투 카메라 고정값(StS2: 플레이어 좌·몬스터 우). KickCombatCamera가 StartCombat에서 재confine에 사용.
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
const RELICS = JSON.parse(readFileSync('data/relics.json', 'utf8'));
@@ -193,78 +143,25 @@ function luaEnemiesTable(enemies) {
`\t${id} = { name = ${luaStr(e.name)}, maxHp = ${e.maxHp}, intents = ${luaIntentsArray(e.intents)} },`);
return `self.Enemies = {\n${lines.join('\n')}\n}`;
}
// Lua 직렬화 헬퍼
function luaStr(s) {
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"';
}
function luaJobsTable(jobs) {
const cls = Object.entries(jobs).map(([clsId, list]) => {
const items = list.map((j) =>
`\t\t{ id = ${luaStr(j.id)}, name = ${luaStr(j.name)}, desc = ${luaStr(j.desc)}, starter = ${luaStr(j.starter)}, tier = ${j.tier ?? 2}, parent = ${luaStr(j.parent ?? clsId)} },`).join('\n');
const items = list.map((j) => `\t\t{ id = ${luaStr(j.id)}, name = ${luaStr(j.name)}, desc = ${luaStr(j.desc)}, starter = ${luaStr(j.starter)} },`).join('\n');
return `\t${clsId} = {\n${items}\n\t},`;
}).join('\n');
return `self.Jobs = {\n${cls}\n}`;
}
function luaClassGroupsTable(groups) {
const rows = Object.entries(groups).map(([clsId, list]) =>
`\t${clsId} = { ${list.map(luaStr).join(', ')} },`).join('\n');
return `self.ClassGroups = {\n${rows}\n}`;
}
function luaClassLineagesTable(lineages) {
const rows = Object.entries(lineages).map(([clsId, list]) =>
`\t${clsId} = { ${list.map(luaStr).join(', ')} },`).join('\n');
return `self.ClassLineages = {\n${rows}\n}`;
}
function luaJobMetaTable(meta) {
const rows = Object.entries(meta).map(([jobId, entry]) =>
`\t${jobId} = { name = ${luaStr(entry.name)}, starter = ${luaStr(entry.starter)}, tier = ${entry.tier}, parent = ${luaStr(entry.parent)}, sourceClass = ${luaStr(entry.sourceClass)} },`);
return `self.JobMeta = {\n${rows.join('\n')}\n}`;
}
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}`);
if (c.damagePerSkillInHand != null) fields.push(`damagePerSkillInHand = ${c.damagePerSkillInHand}`);
if (c.damagePerCardDrawnThisCombat != null) fields.push(`damagePerCardDrawnThisCombat = ${c.damagePerCardDrawnThisCombat}`);
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}`);
if (c.attackPoison != null) fields.push(`attackPoison = ${c.attackPoison}`);
if (c.otherHandAtLeast != null) fields.push(`otherHandAtLeast = ${c.otherHandAtLeast}`);
if (c.bonusHitsWhenOtherHandAtLeast != null) fields.push(`bonusHitsWhenOtherHandAtLeast = ${c.bonusHitsWhenOtherHandAtLeast}`);
if (c.block != null) fields.push(`block = ${c.block}`);
if (c.blockGainMultiplier != null) fields.push(`blockGainMultiplier = ${c.blockGainMultiplier}`);
if (c.blockPerDamageDealtThisTurn != null) fields.push(`blockPerDamageDealtThisTurn = ${c.blockPerDamageDealtThisTurn}`);
if (c.strength != null) fields.push(`strength = ${c.strength}`);
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)}`);
@@ -276,76 +173,13 @@ function luaCardsTable(cards) {
if (c.pierce === true) fields.push('pierce = true');
if (c.selfVuln != null) fields.push(`selfVuln = ${c.selfVuln}`);
if (c.draw != null) fields.push(`draw = ${c.draw}`);
if (c.drawUntilHandSize != null) fields.push(`drawUntilHandSize = ${c.drawUntilHandSize}`);
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');
if (c.drawPerDiscarded != null) fields.push(`drawPerDiscarded = ${c.drawPerDiscarded}`);
if (c.addShiv != null) fields.push(`addShiv = ${c.addShiv}`);
if (c.turnStartShiv != null) fields.push(`turnStartShiv = ${c.turnStartShiv}`);
if (c.turnStartDraw != null) fields.push(`turnStartDraw = ${c.turnStartDraw}`);
if (c.turnStartDiscard != null) fields.push(`turnStartDiscard = ${c.turnStartDiscard}`);
if (c.handCostZeroThisTurn === true) fields.push('handCostZeroThisTurn = true');
if (c.drawDisabledThisTurn === true) fields.push('drawDisabledThisTurn = true');
if (c.addShivPerDiscard === true) fields.push('addShivPerDiscard = true');
if (c.useAllEnergy === true) fields.push('useAllEnergy = true');
if (c.shivDamageBonus != null) fields.push(`shivDamageBonus = ${c.shivDamageBonus}`);
if (c.firstShivDamageBonus != null) fields.push(`firstShivDamageBonus = ${c.firstShivDamageBonus}`);
if (c.shivRetain === true) fields.push('shivRetain = true');
if (c.shivAoe === true) fields.push('shivAoe = true');
if (c.attackDamageVsWeakMultiplier != null) fields.push(`attackDamageVsWeakMultiplier = ${c.attackDamageVsWeakMultiplier}`);
if (c.poisonHits != null) fields.push(`poisonHits = ${c.poisonHits}`);
if (c.poisonRandomTargets === true) fields.push('poisonRandomTargets = true');
if (c.poisonIfTargetPoisoned === true) fields.push('poisonIfTargetPoisoned = true');
if (c.xDamagePerEnergy != null) fields.push(`xDamagePerEnergy = ${c.xDamagePerEnergy}`);
if (c.xWeakPerEnergy != null) fields.push(`xWeakPerEnergy = ${c.xWeakPerEnergy}`);
if (c.nextTurnBlock != null) fields.push(`nextTurnBlock = ${c.nextTurnBlock}`);
if (c.nextTurnDraw != null) fields.push(`nextTurnDraw = ${c.nextTurnDraw}`);
if (c.nextTurnKeepBlock === true) fields.push('nextTurnKeepBlock = true');
if (c.nextTurnAttackMultiplier != null) fields.push(`nextTurnAttackMultiplier = ${c.nextTurnAttackMultiplier}`);
if (c.nextTurnCopies != null) fields.push(`nextTurnCopies = ${c.nextTurnCopies}`);
if (c.nextTurnSelectHandCard === true) fields.push('nextTurnSelectHandCard = true');
if (c.nextTurnSelectPrompt != null) fields.push(`nextTurnSelectPrompt = ${luaStr(c.nextTurnSelectPrompt)}`);
if (c.nextSkillRepeatCount != null) fields.push(`nextSkillRepeatCount = ${c.nextSkillRepeatCount}`);
if (c.nextSkillCostZero === true) fields.push('nextSkillCostZero = true');
if (c.skillCostReductionThisTurn != null) fields.push(`skillCostReductionThisTurn = ${c.skillCostReductionThisTurn}`);
if (c.skillSlyOnPlay === true) fields.push('skillSlyOnPlay = true');
if (c.turnHandSlyCount != null) fields.push(`turnHandSlyCount = ${c.turnHandSlyCount}`);
if (c.combatCostReductionOnPlay != null) fields.push(`combatCostReductionOnPlay = ${c.combatCostReductionOnPlay}`);
if (c.randomTargetEachHit === true) fields.push('randomTargetEachHit = true');
if (c.repeatOnKill === true) fields.push('repeatOnKill = true');
if (c.affectsAllEnemies === true) fields.push('affectsAllEnemies = true');
if (c.removeEnemyBlock === true) fields.push('removeEnemyBlock = true');
if (c.removeEnemyArtifact === true) fields.push('removeEnemyArtifact = true');
if (c.enemyStrengthLossThisTurn != null) fields.push(`enemyStrengthLossThisTurn = ${c.enemyStrengthLossThisTurn}`);
if (c.extraPoisonTicks != null) fields.push(`extraPoisonTicks = ${c.extraPoisonTicks}`);
if (c.poisonApplicationBurstEvery != null) fields.push(`poisonApplicationBurstEvery = ${c.poisonApplicationBurstEvery}`);
if (c.poisonApplicationBurstDamage != null) fields.push(`poisonApplicationBurstDamage = ${c.poisonApplicationBurstDamage}`);
if (c.innate === true) fields.push('innate = true');
if (c.playableWhenDrawPileEmpty === true) fields.push('playableWhenDrawPileEmpty = true');
if (c.sly === true) fields.push('sly = true');
if (c.retain === true) fields.push('retain = true');
if (c.exhaust === true || String(c.desc || '').includes('소멸.')) fields.push('exhaust = true');
@@ -364,11 +198,4 @@ function luaDeckTable(deck) {
return `self.DrawPile = { ${deck.map(luaStr).join(', ')} }`;
}
export {
CARDS, ENEMIES, CLASSES, JOBS, JOB_META, CLASS_GROUPS, CLASS_LINEAGES, SOUL_UNLOCKS,
luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable,
luaCharsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS,
CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable,
luaStr, luaJobsTable, luaClassGroupsTable, luaClassLineagesTable, luaJobMetaTable,
luaCardsTable, luaDeckTable,
};
export { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, luaCharsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable };

View File

@@ -1,55 +1,108 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { buildMonsterInstance } from '../monster/lib/monster-model.mjs';
// 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] };
// 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' },
];
const isMonster = (e) => typeof e.componentNames === 'string' && e.componentNames.includes('script.Monster');
function rng(seed) { let s = seed >>> 0; return () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 4294967296; }; }
function encGuid(nn, idx) {
const n = (nn * 1000 + 500 + idx) >>> 0;
return `${n.toString(16).padStart(8, '0')}-0000-4000-8000-${n.toString(16).padStart(12, '0')}`;
const h8 = n.toString(16).padStart(8, '0');
const h12 = n.toString(16).padStart(12, '0');
return `${h8}-0000-4000-8000-${h12}`;
}
function slotX(group, i, count) {
const [lo, hi] = X_RANGE[group];
return count <= 1 ? (lo + hi) / 2 : lo + (i * (hi - lo)) / (count - 1);
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 patchMap(nn) {
const tag = String(nn).padStart(2, '0');
const file = `map/map${tag}.map`;
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}`);
}
// 준비도 가드: 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;
});
}
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(',');
}
cm.EnemyId = enemyId;
cm.Group = slot.group;
map.ContentProto.Entities.push(m);
});
writeFileSync(file, JSON.stringify(map, null, 2), 'utf8');
const counts = ['combat', 'elite', 'boss'].map((g) => `${g}${(roster[g] || []).length}`).join('/');
return `map${tag}(${counts})`;
return `map${tag}(${combatIds.join('/')}|${eliteIds.join('/')}|${bossId})`;
}
const made = [1, 2, 3, 4, 5].map(patchMap);
const made = MAP_NUMBERS.map(patchMap);
console.log('Encounters:', made.join(', '));

View File

@@ -1,8 +1,10 @@
import { writeFileSync } from 'node:fs';
import { readFileSync, writeFileSync } from 'node:fs';
// 카드 전투용 자기등록 마커 codeblock(CombatMonster) 생성.
// BeginPlay 시 /common 컨트롤러에 자기등록해 인카운터를 구성한다.
// 맵 부착 값(EnemyId/Group)은 gen-map-encounters.mjs가 인스턴스에 직접 기록한다.
// 맵 몬스터에 적 타입(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';
function prop(Type, Name, DefaultValue = 'nil') {
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
@@ -47,5 +49,39 @@ 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();
console.log('CombatMonster codeblock written.');
const patched = MAP_NUMBERS.map(patchMap);
console.log('CombatMonster codeblock written; patched maps:', patched.join(', '));

View File

@@ -1,29 +0,0 @@
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

@@ -1,78 +0,0 @@
// 몬스터 종별 모델(.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

@@ -1,60 +0,0 @@
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

@@ -1,46 +0,0 @@
// 카드 kind ↔ 효과 정합성 정적 검사 (협업자/codex가 카드 추가 후 실행).
// 배경(2026-06-30): kind가 효과와 안 맞으면 카드가 사용불가/死카드가 된다.
// - ResolveCardDrop 라우팅: Attack=몬스터 위 드롭(FindMonsterAtTouch>0 필요) / Skill·Power=위로 스윕 / Status=unplayable.
// → block·유틸만 있고 데미지 없는 카드를 Attack으로 두면 위로 스윕으로 못 쓴다(아이언 바디 사고).
// - PlayCard의 Power 분기는 PlayerPowers 등록만 하고 damage/aoe를 무시한다.
// → Power인데 powerEffect도 power필드도 없으면 재생 시 아무 효과 없는 死카드(분노 사고).
// 사용: node tools/verify/cardkinds.mjs (이상 0 → exit 0, 있으면 목록 + exit 1)
import { readFileSync } from 'node:fs';
const cards = JSON.parse(readFileSync('data/cards.json', 'utf8')).cards;
// Power 카드를 실제로 기능하게 하는 필드(powerEffect 지속효과 + 온플레이/지속 power 필드).
// damage/aoe/block 같은 Attack/Skill 전용 필드는 Power 분기서 무시되므로 제외.
const POWER_FIELDS = [
'powerEffect', 'strength', 'dex', 'thorns', 'intangible',
'turnStartShiv', 'turnStartDraw', 'turnStartDiscard',
'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',
];
const VALID_KINDS = ['Attack', 'Skill', 'Power', 'Status'];
const issues = [];
for (const [id, c] of Object.entries(cards)) {
if (!VALID_KINDS.includes(c.kind)) {
issues.push(`${id}(${c.name}): 미지원 kind="${c.kind}"`);
continue;
}
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)) {
issues.push(`${id}(${c.name}): kind=Power인데 power효과 없음(死카드) → damage/aoe는 Power 분기서 무시, kind 재검토`);
}
}
console.log(`카드 ${Object.keys(cards).length}장 kind↔효과 정합성: 이상 ${issues.length}`);
for (const i of issues) console.log(' ⚠️ ' + i);
console.log(issues.length ? 'RESULT: 정합성 위반 (위 카드 kind 수정 필요)' : 'RESULT: 모든 카드 kind↔효과 일치 ✓');
process.exit(issues.length ? 1 : 0);

View File

@@ -1,34 +0,0 @@
// cb/*.mjs의 `self.<Field> = ` 대입 ↔ gen-slaydeck.mjs 선언 prop 대조.
// 미선언 prop에 대입하면 MSW 런타임 "cannot set X, no such field" → 그 후보를 정적 검출.
// 사용: node tools/verify/cbprops.mjs
import { readFileSync, readdirSync } from 'node:fs';
const orch = readFileSync('tools/deck/gen-slaydeck.mjs', 'utf8');
const declared = new Set();
for (const m of orch.matchAll(/prop\(\s*'[^']*'\s*,\s*'([^']+)'/g)) declared.add(m[1]);
// MSW 빌트인/설정으로 대입 가능한 self 필드(프롭 아님) — 오탐 제외 화이트리스트.
const BUILTIN = new Set(['Entity']);
const dir = 'tools/deck/cb';
const files = readdirSync(dir).filter((f) => f.endsWith('.mjs'));
const assigns = new Map(); // name -> Set(files)
for (const f of files) {
const src = readFileSync(`${dir}/${f}`, 'utf8');
// self.Name = (단, == / ~= / .Y= / [..]= 는 제외)
for (const m of src.matchAll(/self\.([A-Za-z_]\w*)\s*=(?!=)/g)) {
const name = m[1];
if (!assigns.has(name)) assigns.set(name, new Set());
assigns.get(name).add(f);
}
}
const missing = [...assigns.keys()]
.filter((n) => !declared.has(n) && !BUILTIN.has(n))
.sort();
console.log(`선언 prop: ${declared.size} | 대입된 self.X distinct: ${assigns.size}`);
console.log(`미선언 대입 (no such field 후보): ${missing.length}`);
for (const n of missing) console.log(` - ${n} [${[...assigns.get(n)].join(', ')}]`);
console.log(missing.length ? 'RESULT: MISSING PROPS ABOVE' : 'RESULT: 모든 self 대입이 선언됨 ✓');
process.exit(missing.length ? 1 : 0);

View File

@@ -1,40 +0,0 @@
// 순서 무관 codeblock 메서드 집합 비교. 본문 미출력 — 이름·차이 카운트만.
// 메서드 이동 리팩터의 무손실 검증용: 워킹트리 codeblock vs ref(기본 HEAD).
// 사용: node tools/verify/cbset.mjs [ref]
import { readFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
const PATH = 'RootDesk/MyDesk/SlayDeckController.codeblock';
const ref = process.argv[2] || 'HEAD';
function methodsOf(jsonText) {
const obj = JSON.parse(jsonText);
const arr = obj.ContentProto.Json.Methods;
const map = new Map();
for (const m of arr) {
map.set(m.Name, { code: m.Code, exec: m.ExecSpace, params: JSON.stringify(m.Parameters || []) });
}
return map;
}
const work = methodsOf(readFileSync(PATH, 'utf8'));
const base = methodsOf(execSync(`git show ${ref}:${PATH}`, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }));
const onlyWork = [...work.keys()].filter((k) => !base.has(k));
const onlyBase = [...base.keys()].filter((k) => !work.has(k));
const changed = [];
for (const k of work.keys()) {
if (!base.has(k)) continue;
const a = work.get(k);
const b = base.get(k);
if (a.code !== b.code || a.exec !== b.exec || a.params !== b.params) changed.push(k);
}
console.log(`ref=${ref} work=${work.size} base=${base.size}`);
console.log(`only-in-work (${onlyWork.length}): ${onlyWork.join(', ') || '-'}`);
console.log(`only-in-base (${onlyBase.length}): ${onlyBase.join(', ') || '-'}`);
console.log(`body/exec/params changed (${changed.length}): ${changed.join(', ') || '-'}`);
const ok = onlyWork.length === 0 && onlyBase.length === 0 && changed.length === 0;
console.log(ok ? 'RESULT: IDENTICAL SET (무손실)' : 'RESULT: DIFFERENCES ABOVE');
process.exit(ok ? 0 : 1);

View File

@@ -1,82 +0,0 @@
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');
}