From eb06663758366a0544844399a6634b2aa7769697 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:12:57 +0900 Subject: [PATCH 1/7] =?UTF-8?q?docs(buffs-power):=20P6=20=EC=84=A4?= =?UTF-8?q?=EA=B3=84=20=E2=80=94=20=EB=B2=84=ED=94=84/=EB=94=94=EB=B2=84?= =?UTF-8?q?=ED=94=84=C2=B7Power=20=EC=B9=B4=EB=93=9C=C2=B7=EC=A0=81=20?= =?UTF-8?q?=EB=B0=A9=EC=96=B4=EB=8F=84=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../specs/2026-06-12-buffs-power-design.md | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-12-buffs-power-design.md diff --git a/docs/superpowers/specs/2026-06-12-buffs-power-design.md b/docs/superpowers/specs/2026-06-12-buffs-power-design.md new file mode 100644 index 0000000..e44db7e --- /dev/null +++ b/docs/superpowers/specs/2026-06-12-buffs-power-design.md @@ -0,0 +1,107 @@ +# P6 — 버프/디버프·Power 카드·적 방어도 UI 설계 + +날짜: 2026-06-12 +브랜치: `feature/p6-buffs-power` +근거: 덱빌딩 코어 확장 — Slay the Spire 2의 약화·취약·힘 시스템과 Power 카드 종류를 메이플 IP로 이식. 적 방어도 미표시 문제 해결. + +## 범위 + +1. **버프/디버프 3종** — 약화(Weak)·취약(Vulnerable)·힘(Strength), StS 표준 수치 +2. **Power 카드 kind** — 전투 동안 지속되는 효과, 사용 시 소멸(전투 한정) +3. **적 방어도 UI** — 적 슬롯에 플레이어와 동일한 방어도 배지 표시 +4. **예시 카드 4종** — 메이플 스킬명, StS 카드 효과 매핑 +5. **적 디버프 인텐트** — 일부 적이 플레이어에게 약화/취약 부여 (양방향 시스템) + +비범위: 물약·유물 강화(P7), 카드 강화(업그레이드), 민첩(Dexterity). + +## 규칙 (StS 표준) + +| 상태 | 효과 | 지속 | +|------|------|------| +| 힘(Strength) | 공격 피해 +N | 전투 동안 영구 | +| 약화(Weak) | 주는 공격 피해 25% 감소 (floor) | N턴, 자기 턴 종료 시 1 감소 | +| 취약(Vulnerable) | 받는 공격 피해 50% 증가 (floor) | N턴, 자기 턴 종료 시 1 감소 | + +피해 공식 (플레이어 공격): `floor( floor((base + 힘) × (약화 ? 0.75 : 1)) × (대상 취약 ? 1.5 : 1) )` +피해 공식 (적 공격): 동일 공식을 적 힘·적 약화·플레이어 취약으로 적용. + +감소 타이밍: +- 플레이어 약화/취약 → `EndPlayerTurn`에서 1 감소 +- 적 약화/취약 → 적 개별 행동(`EnemyActStep`) 종료 시 1 감소 + +## 상태 모델 + +- 플레이어: `PlayerStr` / `PlayerWeak` / `PlayerVuln` (number props), `PlayerPowers` (any) +- 몬스터: `m.str` / `m.weak` / `m.vuln` (BuildMonsters에서 0 초기화) +- 전투 시작(`StartCombat`) 시 전부 리셋 + +## 카드 데이터 스키마 확장 (`data/cards.json`) + +| 필드 | 의미 | +|------|------| +| `kind: "Power"` | 파워 카드 — 사용 시 소멸, 전투 동안 지속 효과 | +| `weak: N` | 대상 적에게 약화 N 부여 | +| `vuln: N` | 대상 적에게 취약 N 부여 | +| `strength: N` | 자신에게 힘 +N | +| `powerEffect: "strengthPerTurn"`, `value: N` | 파워: 매 턴 시작 시 힘 +N | + +`luaCardsTable`이 신규 필드를 직렬화하도록 확장. + +## 예시 카드 4종 (메이플 스킬명 × StS 효과) + +| id | 이름 | kind | 코스트 | 효과 | StS 원본 | +|----|------|------|--------|------|----------| +| ChargedBlow | 차지 블로우 | Attack | 2 | 피해 8, 취약 2 | Bash | +| Threaten | 위협 | Skill | 0 | 타겟 적에게 약화 2 | Intimidate 계열 | +| Enrage | 인레이지 | Skill | 1 | 힘 +2 | Inflame | +| Rage | 분노 | Power | 1 | 매 턴 시작 시 힘 +1 | Demon Form(경량) | + +- 보상 풀은 `self.Cards` 전체 자동 편입이므로 추가 작업 없음. 시작 덱 변경 없음. +- 카드 이미지는 MSW 공식 리소스 RUID를 검색·선별해 사용 (계정 업로드 리소스는 로컬 워크스페이스에서 흰 박스 — 금지). +- 타겟팅: Attack은 기존 드래그 타겟, 디버프 Skill(위협)은 현재 `TargetIndex` 적에게 적용 (적 클릭으로 타겟 변경 가능). + +## Power 카드 동작 + +- `PlayCard`에서 `kind == "Power"` 분기: `powerEffect` 등록(`PlayerPowers`에 push) 후 **버린 덱에 넣지 않고 소멸** (RunDeck에는 유지 — 다음 전투에서 다시 사용 가능) +- `StartPlayerTurn`: `PlayerPowers` 순회, `strengthPerTurn`이면 `PlayerStr += value` +- 카드 면 색: 기존 `ApplyCardFace`의 else 분기(초록)가 Power에 적용됨 — 명시적으로 `elseif c.kind == "Power"` 초록 지정 + +## 적 디버프 인텐트 (`data/enemies.json`) + +인텐트 스키마 확장: `{ "kind": "Debuff", "effect": "weak"|"vuln", "value": N }` + +| 적 | 추가 인텐트 | +|----|------------| +| mushmom (머쉬맘) | 포자 — 약화 2 | +| slime_elite (정예 슬라임) | 약화 1 | +| slime_boss (슬라임 킹) | 취약 2 | +| king_slime (킹 슬라임) | 취약 2 | +| modified_snail (변형된 달팽이) | 약화 1 | + +`EnemyActStep`에서 `kind == "Debuff"` 처리: `PlayerWeak/PlayerVuln += value`. + +## UI 변경 (`gen-slaydeck.mjs` UI 생성부) + +1. **적 방어도 배지**: 각 `MonsterSlot{i}`에 `BlockBadge`(파란 사각 44×40, HP바 좌측 x=-HP_BAR_W/2-32, y=-14) + `BlockBadge/Value` 텍스트. `RenderCombat`에서 `m.block > 0`일 때만 표시 — 플레이어 배지와 동일 패턴. +2. **적 버프 라인**: `MonsterSlot{i}/Buffs` 텍스트 (Intent 아래 y=-58, fontSize 15, 보라). 예: `힘+2 약화1 취약2`. 없으면 빈 문자열. +3. **플레이어 버프 라인**: `PlayerPanel/Buffs` 텍스트 (y=-44, fontSize 14). 파워 활성 시 `분노` 포함. 예: `힘+3 취약1 · 분노`. +4. **인텐트 표시 확장**: `Debuff` 인텐트 → `약화 2 부여` / `취약 2 부여`, 보라색. Attack 인텐트 숫자는 힘·약화·플레이어 취약 반영한 **최종 예상치** 표시 (StS 동일). + +## 밸런스 시뮬 동기화 (`tools/balance/sim-balance.mjs`) + +- 카드: `strength`/`weak`/`vuln`/Power 처리 재현 (chooseAction은 Attack 우선 휴리스틱 유지, Power/버프 스킬은 에너지 남을 때 사용하는 단순 규칙 추가) +- 적: `Debuff` 인텐트 재현 (플레이어 weak/vuln) +- 피해 공식 양방향 동일 적용, 감소 타이밍 동일 +- 기존 테스트(`sim-balance.test.mjs`) 통과 + 신규 케이스(약화/취약/힘 계산 단위 테스트) 추가 + +## 검증 + +1. `node tools/deck/gen-slaydeck.mjs` 성공 (산출물 재생성: SlayDeckController.codeblock·DefaultGroup.ui·common.gamelogic) +2. `node --test tools/balance/sim-balance.test.mjs` 통과 +3. `node tools/balance/sim-balance.mjs` 실행 — 승률이 0%/100% 극단으로 붕괴하지 않는지 확인 + +## 결정 사항 + +- 민첩(Dexterity)은 도입하지 않음 (방어 카드 수가 적어 효용 낮음 — YAGNI) +- 기존 카드(슬래시 블러스트 등) 수치 변경 없음 — 신규 카드로만 확장 +- Power는 1종으로 시작, 물약/유물(P7)과의 연계는 P7에서 From 12f3928ab43bfe4b09b3fe3f627e17e434fb0427 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:16:05 +0900 Subject: [PATCH 2/7] =?UTF-8?q?docs(buffs-power):=20P6=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EA=B3=84=ED=9A=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-06-12-buffs-power.md | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-12-buffs-power.md diff --git a/docs/superpowers/plans/2026-06-12-buffs-power.md b/docs/superpowers/plans/2026-06-12-buffs-power.md new file mode 100644 index 0000000..656c0d7 --- /dev/null +++ b/docs/superpowers/plans/2026-06-12-buffs-power.md @@ -0,0 +1,377 @@ +# P6 — 버프/디버프·Power 카드·적 방어도 UI 구현 계획 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** StS 표준 약화·취약·힘 버프 시스템 + Power 카드 kind + 적 방어도 배지 UI를 데이터 주도로 구현. + +**Architecture:** `data/cards.json`·`enemies.json` 스키마 확장 → `tools/deck/gen-slaydeck.mjs`의 Lua 생성부(상태 props·전투 메서드·UI 엔티티) 확장 → 산출물 재생성. 밸런스 시뮬(`tools/balance/sim-balance.mjs`)에 동일 규칙 재현. + +**Tech Stack:** Node.js(생성기·시뮬), MSW Lua(생성물), node:test. + +설계 문서: `docs/superpowers/specs/2026-06-12-buffs-power-design.md` + +--- + +### Task 1: 신규 카드 이미지 RUID 선별 (메이커) + +**Files:** 없음 (RUID 4개 확보가 산출물) + +- [ ] **Step 1**: `asset_search_resources`(cat=sprite, source=maplestory)로 후보 수집 — 쿼리: "차지 블로우", "위협", "인레이지", "분노" (결과 빈약 시 "스킬", "버프" 등 보조 쿼리) +- [ ] **Step 2**: 메이커 Play Test 상태에서 `maker_execute_script`(client)로 후보 RUID를 UIGroup에 격자 배치(아래 패턴) 후 `maker_screenshot`으로 확인, 카드당 1개 선별 + +```lua +-- 후보 미리보기 패턴 (client 컨텍스트) +local ruids = { "", "", "..." } +local root = _EntityService:GetEntityByPath("/ui/DefaultGroup") +for i = 1, #ruids do + local e = _SpawnService:SpawnByModelId("model://uisprite", "RuidPreview" .. i, Vector3(0,0,0), root) + e.SpriteGUIRendererComponent.ImageRUID = ruids[i] + e.UITransformComponent.anchoredPosition = Vector2(-600 + ((i-1) % 8) * 160, 200 - math.floor((i-1) / 8) * 160) + e.UITransformComponent.RectSize = Vector2(140, 140) +end +``` + +- [ ] **Step 3**: 미리보기 엔티티 제거(스크립트로 Destroy) 후 플레이 종료 + +### Task 2: 카드·적 데이터 확장 + +**Files:** +- Modify: `data/cards.json` +- Modify: `data/enemies.json` + +- [ ] **Step 1**: `data/cards.json`의 `cards`에 4종 추가 (image는 Task 1 선별값) + +```json +"ChargedBlow": { "name": "차지 블로우", "cost": 2, "kind": "Attack", "damage": 8, "vuln": 2, "desc": "피해 8, 취약 2", "image": "<선별RUID>" }, +"Threaten": { "name": "위협", "cost": 0, "kind": "Skill", "weak": 2, "desc": "약화 2 부여", "image": "<선별RUID>" }, +"Enrage": { "name": "인레이지", "cost": 1, "kind": "Skill", "strength": 2, "desc": "힘 +2", "image": "<선별RUID>" }, +"Rage": { "name": "분노", "cost": 1, "kind": "Power", "powerEffect": "strengthPerTurn", "value": 1, "desc": "매 턴 시작 시 힘 +1", "image": "<선별RUID>" } +``` + +- [ ] **Step 2**: `data/enemies.json` 인텐트에 Debuff 추가 — mushmom에 `{ "kind": "Debuff", "effect": "weak", "value": 2 }` (intents 2번째로 삽입), slime_elite에 `{ "kind": "Debuff", "effect": "weak", "value": 1 }` (마지막), slime_boss·king_slime에 `{ "kind": "Debuff", "effect": "vuln", "value": 2 }` (Defend 다음), modified_snail에 `{ "kind": "Debuff", "effect": "weak", "value": 1 }` (마지막) +- [ ] **Step 3**: 커밋 `feat(buffs-power): 신규 카드 4종·적 디버프 인텐트 데이터` + +### Task 3: 생성기 — 직렬화·상태·전투 규칙 + +**Files:** +- Modify: `tools/deck/gen-slaydeck.mjs` (luaCardsTable ~line 64, props ~1888, StartCombat ~2001, BuildMonsters ~2040, StartPlayerTurn ~2184, EndPlayerTurn ~2191, PlayCard ~2410, DealDamageToTarget ~2520, EnemyActStep ~2599, ApplyCardFace ~2347) + +- [ ] **Step 1**: `luaCardsTable`에 신규 필드 직렬화 추가 + +```js +if (c.strength != null) fields.push(`strength = ${c.strength}`); +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)}`); +if (c.value != null) fields.push(`value = ${c.value}`); +``` + +- [ ] **Step 2**: props 추가 — `prop('number', 'PlayerStr', '0')`, `prop('number', 'PlayerWeak', '0')`, `prop('number', 'PlayerVuln', '0')`, `prop('any', 'PlayerPowers')` +- [ ] **Step 3**: `StartCombat`에 리셋 추가 (`self.PlayerBlock = 0` 다음 줄) + +```lua +self.PlayerStr = 0 +self.PlayerWeak = 0 +self.PlayerVuln = 0 +self.PlayerPowers = {} +``` + +- [ ] **Step 4**: `BuildMonsters`의 몬스터 테이블 생성에 `str = 0, weak = 0, vuln = 0` 필드 추가 (기존 `block = 0` 자리 옆) +- [ ] **Step 5**: 플레이어 공격 피해 헬퍼 `CalcPlayerAttack` 메서드 신설 + `PlayCard`의 Attack 분기를 수정 — `c.damage`에 힘·약화 적용한 값을 `PlayAttackFx`에 전달. 버프 필드 공통 처리(Attack/Skill 양쪽): `strength`/`weak`/`vuln` 적용 + +```lua +-- method CalcPlayerAttack(base) → number +local dmg = base + self.PlayerStr +if self.PlayerWeak > 0 then + dmg = math.floor(dmg * 0.75) +end +return dmg +``` + +```lua +-- PlayCard 내 교체 (Attack 분기) +if c.kind == "Attack" then + if c.damage ~= nil then + self:PlayAttackFx(self.TargetIndex, c.image, self:CalcPlayerAttack(c.damage)) + end + if c.block ~= nil then + self.PlayerBlock = self.PlayerBlock + c.block + end + self:ApplyRelics("cardPlayed") +elseif c.kind == "Skill" then + if c.block ~= nil then + self.PlayerBlock = self.PlayerBlock + c.block + end +elseif c.kind == "Power" then + if c.powerEffect ~= nil then + table.insert(self.PlayerPowers, cardId) + end +end +-- 공통 버프/디버프 적용 (kind 분기 아래, table.remove 위) +if c.strength ~= nil then + self.PlayerStr = self.PlayerStr + c.strength +end +if c.weak ~= nil or c.vuln ~= nil then + local tm = self.Monsters[self.TargetIndex] + if tm ~= nil and tm.alive == true then + if c.weak ~= nil then tm.weak = tm.weak + c.weak end + if c.vuln ~= nil then tm.vuln = tm.vuln + c.vuln end + end +end +``` + +- [ ] **Step 6**: Power 소멸 처리 — `PlayCard`의 `table.insert(self.DiscardPile, cardId)`를 조건부로 + +```lua +table.remove(self.Hand, slot) +if c.kind ~= "Power" then + table.insert(self.DiscardPile, cardId) +end +``` + +- [ ] **Step 7**: `DealDamageToTarget`에 취약 배수 (block 차감 **이전**) + +```lua +local dmg = amount +if m.vuln > 0 then + dmg = math.floor(dmg * 1.5) +end +``` + +- [ ] **Step 8**: `EnemyActStep` — Debuff 인텐트 처리 + 적 공격 피해 공식 + 행동 후 적 디버프 감소 + +```lua +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 + local before = self.PlayerHp + self:DealDamageToPlayer(atk) + self:ShowPlayerDmgPop(before - self.PlayerHp) +elseif intent.kind == "Defend" then + m.block = m.block + intent.value +elseif intent.kind == "Debuff" then + if intent.effect == "weak" then + self.PlayerWeak = self.PlayerWeak + intent.value + elseif intent.effect == "vuln" then + self.PlayerVuln = self.PlayerVuln + intent.value + end +end +-- intentIdx 갱신 직후 +if m.weak > 0 then m.weak = m.weak - 1 end +if m.vuln > 0 then m.vuln = m.vuln - 1 end +``` + +- [ ] **Step 9**: `EndPlayerTurn`에 플레이어 디버프 감소 (`self:EnemyTurn()` 직전) + +```lua +if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end +if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end +``` + +- [ ] **Step 10**: `StartPlayerTurn`에 파워 발동 (`self:ApplyRelics("turnStart")` 다음) + +```lua +if self.PlayerPowers ~= nil then + for i = 1, #self.PlayerPowers do + local pc = self.Cards[self.PlayerPowers[i]] + if pc ~= nil and pc.powerEffect == "strengthPerTurn" then + self.PlayerStr = self.PlayerStr + pc.value + end + end +end +``` + +- [ ] **Step 11**: `ApplyCardFace` kind 색 분기에 `elseif c.kind == "Power" then` → `Color(0.46, 0.68, 0.52, 1)` 명시 (기존 else를 Power로) +- [ ] **Step 12**: 커밋 `feat(buffs-power): 버프/디버프·Power 전투 규칙 (생성기)` + +### Task 4: 생성기 — UI (적 방어도 배지·버프 라인·인텐트) + +**Files:** +- Modify: `tools/deck/gen-slaydeck.mjs` (몬스터 슬롯 UI 생성부 ~line 916-1015, PlayerPanel ~1015-1100, RenderCombat ~2688) + +- [ ] **Step 1**: 몬스터 슬롯 루프에 BlockBadge(guid 270+i)·Value(guid 280+i)·Buffs(guid 290+i) 엔티티 추가 (DmgPop 추가 코드 다음) + +```js +const mBlockBadge = entity({ + id: guid('cmb', 270 + i), path: `${base}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 6, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 36 }, pos: { x: -HP_BAR_W / 2 - 30, y: -14 } }), + sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }), + ], +}); +mBlockBadge.jsonString.enable = false; +combat.push(mBlockBadge); +combat.push(entity({ + id: guid('cmb', 280 + i), path: `${base}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 40, parentH: 36, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 32 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value: '0', fontSize: 17, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], +})); +combat.push(entity({ + id: guid('cmb', 290 + i), path: `${base}/Buffs`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 7, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 60, y: 22 }, pos: { x: 0, y: -58 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 15, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), + ], +})); +``` + +- [ ] **Step 2**: PlayerPanel에 Buffs 텍스트(guid 217) 추가 (BlockBadge/Value 다음) + +```js +combat.push(entity({ + id: guid('cmb', 217), path: `${PP}/Buffs`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 6, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 22 }, pos: { x: 0, y: -44 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 14, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), + ], +})); +``` + +- [ ] **Step 3**: 버프 문자열 헬퍼 메서드 `BuffsLabel` 신설 (Lua, str/weak/vuln → "힘+2 약화1 취약2") + +```lua +-- method BuffsLabel(str, weak, vuln) → string +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 +return table.concat(parts, " ") +``` + +- [ ] **Step 4**: `RenderCombat` 확장 — 몬스터 루프 내(SetHpBar 다음) + +```lua +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)) +``` + +인텐트 분기 교체 (Attack은 최종 예상치·Debuff 추가): + +```lua +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 + end +end +``` + +인텐트 색: Attack 빨강(기존), Defend 파랑(기존 else), Debuff 보라 `Color(0.8, 0.5, 1, 1)` 분기 추가. + +플레이어 표시 (기존 BlockBadge 갱신 다음): + +```lua +local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln) +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/DefaultGroup/CombatHud/PlayerPanel/Buffs", pb) +``` + +- [ ] **Step 5**: 커밋 `feat(buffs-power): 적 방어도 배지·버프 라인·디버프 인텐트 UI (생성기)` + +### Task 5: 밸런스 시뮬 동기화 + 테스트 + +**Files:** +- Modify: `tools/balance/sim-balance.mjs` +- Test: `tools/balance/sim-balance.test.mjs` + +- [ ] **Step 1**: 실패 테스트 먼저 추가 — 약화·취약·힘 계산 + Debuff 인텐트 + Power 동작 + +```js +test('simulateCombat: 취약이 플레이어 공격을 1.5배로', () => { + const data = { + cards: { Vuln: { name: '취약기', cost: 1, kind: 'Skill', vuln: 9 }, Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 10 } }, + starterDeck: ['Vuln', 'Hit', 'Hit', 'Hit', 'Hit'], + monsters: [{ name: '적', maxHp: 100, intents: [{ kind: 'Defend', value: 0 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + // 1턴: 공격 우선 휴리스틱 → Hit×3 (취약 미부여, 30) — 그래도 30+α로 수치 검증은 별도 단위 함수로 + assert.equal(typeof r.win, 'boolean'); +}); + +test('calcAttack: 힘·약화·취약 공식', () => { + assert.equal(calcAttack(6, 2, 0, 0), 8); // 힘+2 + assert.equal(calcAttack(6, 0, 1, 0), 4); // 약화 floor(6*0.75) + assert.equal(calcAttack(6, 0, 0, 1), 9); // 취약 floor(6*1.5) + assert.equal(calcAttack(10, 2, 1, 1), 13); // floor(floor(12*0.75)=9 → floor(9*1.5)=13 +}); + +test('simulateCombat: 적 Debuff 인텐트로 플레이어 약화 → 받는 피해 감소 검증', () => { + const data = { + cards: { Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 1 } }, + starterDeck: ['Hit', 'Hit', 'Hit', 'Hit', 'Hit'], + monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Debuff', effect: 'weak', value: 1 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + assert.equal(r.playerHpRemaining, 80); // Debuff만 하는 적 → 피해 0 +}); + +test('simulateCombat: Power(매턴 힘) 누적', () => { + const data = { + cards: { + Rage: { name: '분노', cost: 1, kind: 'Power', powerEffect: 'strengthPerTurn', value: 5 }, + Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 1 }, + }, + starterDeck: ['Rage', 'Hit', 'Hit', 'Hit', 'Hit'], + monsters: [{ name: '적', maxHp: 60, intents: [{ kind: 'Defend', value: 0 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + assert.equal(r.win, true); + assert.ok(r.turns <= 6, `파워 누적으로 빠른 처치 기대, 실제 ${r.turns}턴`); +}); +``` + +- [ ] **Step 2**: `node --test tools/balance/sim-balance.test.mjs` → 신규 테스트 FAIL 확인 +- [ ] **Step 3**: `sim-balance.mjs` 구현 — `calcAttack(base, str, weak, vulnOnTarget)` export 신설, `simulateCombat`에 pStr/pWeak/pVuln/powers·몬스터 str/weak/vuln 상태 추가, 규칙 재현(부여→감소 타이밍 Lua와 동일), `chooseAction` 확장(Attack 우선 유지, 잔여 에너지로 Power→버프 Skill 사용), Debuff 인텐트 처리. `formatReport`의 kind 루프에 'Power' 포함(효율 계산은 plays만 표시). +- [ ] **Step 4**: `node --test tools/balance/sim-balance.test.mjs` → 전체 PASS +- [ ] **Step 5**: 커밋 `feat(buffs-power): 밸런스 시뮬 버프/디버프·Power 동기화` + +### Task 6: 산출물 재생성·시뮬 확인·푸시·PR·머지 + +**Files:** +- Regen: `RootDesk/MyDesk/SlayDeckController.codeblock`, `ui/DefaultGroup.ui`, `Global/common.gamelogic` + +- [ ] **Step 1**: `node tools/deck/gen-slaydeck.mjs` 실행 성공 확인 +- [ ] **Step 2**: `node tools/balance/sim-balance.mjs` — 승률 0%/100% 극단 아님 확인 (참고용 리포트 기록) +- [ ] **Step 3**: 커밋 `feat(buffs-power): 산출물 재생성 (버프/디버프·Power·적 방어도 UI)` +- [ ] **Step 4**: `git push -u origin feature/p6-buffs-power` +- [ ] **Step 5**: Gitea API로 PR 생성 → 머지 (기존 자동화 패턴: `curl -s -X POST .../repos/gahusb/maplecontest/pulls`, 토큰은 `.mcp.json` 참조 금지 — `git credential` 또는 기존 사용 토큰 경로) + +## Self-Review 결과 + +- 설계 요구 전 항목(버프 3종·Power·적 방어도 배지·예시 카드 4종·적 디버프 인텐트·시뮬 동기화) 태스크 매핑 확인 +- 타입/이름 일관성: `CalcPlayerAttack`·`BuffsLabel`·`PlayerStr/Weak/Vuln`·`PlayerPowers`·`m.str/weak/vuln` 통일 확인 +- 플레이스홀더: 카드 image RUID만 Task 1 산출물에 의존 (의도된 순서) From 2bf5716fce8695745bf529fd260ebcc2ade0b178 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:25:02 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat(buffs-power):=20=EC=8B=A0=EA=B7=9C=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=204=EC=A2=85=C2=B7=EC=A0=81=20=EB=94=94?= =?UTF-8?q?=EB=B2=84=ED=94=84=20=EC=9D=B8=ED=85=90=ED=8A=B8=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 차지 블로우(피해8·취약2)·위협(약화2)·인레이지(힘+2)·분노(Power, 매턴 힘+1) - 카드 이미지 RUID 메이커 선별 (공식 리소스) - 정예 슬라임·머쉬맘·변형된 달팽이(약화), 슬라임 킹·킹 슬라임(취약) 디버프 인텐트 Co-Authored-By: Claude Opus 4.8 (1M context) --- data/cards.json | 34 ++++++++++++++++++++++++++++++++++ data/enemies.json | 9 +++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/data/cards.json b/data/cards.json index 6a09f5a..fd79898 100644 --- a/data/cards.json +++ b/data/cards.json @@ -40,6 +40,40 @@ "damage": 13, "desc": "피해 13", "image": "21af4bccc5054a5dbc8245dfa7f08681" + }, + "ChargedBlow": { + "name": "차지 블로우", + "cost": 2, + "kind": "Attack", + "damage": 8, + "vuln": 2, + "desc": "피해 8, 취약 2", + "image": "fe83c7635b0e49ed83d75a2833adb53e" + }, + "Threaten": { + "name": "위협", + "cost": 0, + "kind": "Skill", + "weak": 2, + "desc": "약화 2 부여", + "image": "64daadf1a98e490d9c14ef52ec776e63" + }, + "Enrage": { + "name": "인레이지", + "cost": 1, + "kind": "Skill", + "strength": 2, + "desc": "힘 +2", + "image": "09370fc7551e47238fd103a80fba558e" + }, + "Rage": { + "name": "분노", + "cost": 1, + "kind": "Power", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "매 턴 시작 시 힘 +1", + "image": "379d86e3de064959aa4612f71e84ccfb" } }, "starterDeck": [ diff --git a/data/enemies.json b/data/enemies.json index c051043..668886c 100644 --- a/data/enemies.json +++ b/data/enemies.json @@ -15,7 +15,8 @@ "intents": [ { "kind": "Attack", "value": 14 }, { "kind": "Attack", "value": 8 }, - { "kind": "Defend", "value": 10 } + { "kind": "Defend", "value": 10 }, + { "kind": "Debuff", "effect": "weak", "value": 1 } ] }, "slime_boss": { @@ -24,6 +25,7 @@ "intents": [ { "kind": "Attack", "value": 18 }, { "kind": "Defend", "value": 12 }, + { "kind": "Debuff", "effect": "vuln", "value": 2 }, { "kind": "Attack", "value": 10 }, { "kind": "Attack", "value": 22 } ] @@ -70,6 +72,7 @@ "maxHp": 75, "intents": [ { "kind": "Defend", "value": 10 }, + { "kind": "Debuff", "effect": "weak", "value": 2 }, { "kind": "Attack", "value": 16 }, { "kind": "Attack", "value": 9 }, { "kind": "Defend", "value": 6 } @@ -82,7 +85,8 @@ { "kind": "Attack", "value": 12 }, { "kind": "Defend", "value": 8 }, { "kind": "Attack", "value": 7 }, - { "kind": "Attack", "value": 14 } + { "kind": "Attack", "value": 14 }, + { "kind": "Debuff", "effect": "weak", "value": 1 } ] }, "king_slime": { @@ -91,6 +95,7 @@ "intents": [ { "kind": "Attack", "value": 18 }, { "kind": "Defend", "value": 14 }, + { "kind": "Debuff", "effect": "vuln", "value": 2 }, { "kind": "Attack", "value": 12 }, { "kind": "Attack", "value": 24 } ] From 6df3fcf01c58a88341383cdbf33effb1856295d2 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:28:03 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat(buffs-power):=20=EB=B2=84=ED=94=84/?= =?UTF-8?q?=EB=94=94=EB=B2=84=ED=94=84=C2=B7Power=20=EC=A0=84=ED=88=AC=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20(=EC=83=9D=EC=84=B1=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 약화(-25%)·취약(+50%)·힘(+N) 피해 공식 양방향 적용 (CalcPlayerAttack·EnemyActStep) - Power 카드: 사용 시 소멸, PlayerPowers 등록, 매턴 strengthPerTurn 발동 - 적 Debuff 인텐트 처리·디버프 턴 감소(플레이어 턴 종료·적 행동 후) - 인텐트 effect 직렬화·막 배율은 Debuff 값 제외 Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/deck/gen-slaydeck.mjs | 99 +++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index d5774b7..4724d4f 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -37,7 +37,11 @@ function luaRelicsTable(relics) { } function luaIntentsArray(intents) { - return '{ ' + intents.map((it) => `{ kind = ${luaStr(it.kind)}, value = ${it.value} }`).join(', ') + ' }'; + return '{ ' + intents.map((it) => { + const fields = [`kind = ${luaStr(it.kind)}`, `value = ${it.value}`]; + if (it.effect != null) fields.push(`effect = ${luaStr(it.effect)}`); + return `{ ${fields.join(', ')} }`; + }).join(', ') + ' }'; } function luaEnemiesTable(enemies) { const lines = Object.entries(enemies).map(([id, e]) => @@ -65,6 +69,11 @@ function luaCardsTable(cards) { 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.block != null) fields.push(`block = ${c.block}`); + if (c.strength != null) fields.push(`strength = ${c.strength}`); + 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)}`); + if (c.value != null) fields.push(`value = ${c.value}`); if (c.image != null) fields.push(`image = ${luaStr(c.image)}`); return `\t${id} = { ${fields.join(', ')} },`; }); @@ -1888,6 +1897,10 @@ function writeCodeblocks() { prop('number', 'DragSlot', '0'), prop('boolean', 'FxBusy', 'false'), prop('boolean', 'TurnBusy', 'false'), + prop('number', 'PlayerStr', '0'), + prop('number', 'PlayerWeak', '0'), + prop('number', 'PlayerVuln', '0'), + prop('any', 'PlayerPowers'), ], [ method('OnBeginPlay', `self:ShowMainMenu()`), method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false) @@ -2001,6 +2014,10 @@ self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false) self.MaxEnergy = 3 self.Turn = 0 self.PlayerBlock = 0 +self.PlayerStr = 0 +self.PlayerWeak = 0 +self.PlayerVuln = 0 +self.PlayerPowers = {} self.CombatOver = false self.DiscardPile = {} self.Hand = {} @@ -2062,11 +2079,16 @@ for i = 1, n do if e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = "Attack", value = 5 } } } end local intents = {} for k = 1, #e.intents do - intents[k] = { kind = e.intents[k].kind, value = math.floor(e.intents[k].value * mult) } + local v = e.intents[k].value + if e.intents[k].kind ~= "Debuff" then + v = math.floor(v * mult) + end + intents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect } end local maxHp = math.floor(e.maxHp * mult) self.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name, - hp = maxHp, maxHp = maxHp, block = 0, intents = intents, intentIdx = 1, alive = true, slot = i } + hp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, + intents = intents, intentIdx = 1, alive = true, slot = i } self:ReviveMonsterEntity(item.entity) self:PositionMonsterSlot(i) end @@ -2184,6 +2206,14 @@ end`), method('StartPlayerTurn', `self.Turn = self.Turn + 1 self.Energy = self.MaxEnergy self:ApplyRelics("turnStart") +if self.PlayerPowers ~= nil then + for i = 1, #self.PlayerPowers do + local pc = self.Cards[self.PlayerPowers[i]] + if pc ~= nil and pc.powerEffect == "strengthPerTurn" then + self.PlayerStr = self.PlayerStr + pc.value + end + end +end self.PlayerBlock = 0 self:DrawCards(5) self:RenderHand(true) @@ -2195,6 +2225,8 @@ for i = 1, #self.Hand do \ttable.insert(self.DiscardPile, self.Hand[i]) end self.Hand = {} +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:EnemyTurn()`), @@ -2351,10 +2383,10 @@ local e = _EntityService:GetEntityByPath(base) if e ~= nil and e.SpriteGUIRendererComponent ~= nil then if c.kind == "Attack" then e.SpriteGUIRendererComponent.Color = Color(0.86, 0.42, 0.38, 1) - elseif c.kind == "Skill" then - e.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1) - else + elseif c.kind == "Power" then e.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1) + else + e.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1) end end self:SetText(base .. "/Cost", string.format("%d", c.cost)) @@ -2407,6 +2439,14 @@ end, 1 / 60)`, [ { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toPos' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'duration' }, ]), + method('CalcPlayerAttack', `local dmg = base + self.PlayerStr +if self.PlayerWeak > 0 then + dmg = math.floor(dmg * 0.75) +end +if dmg < 0 then + dmg = 0 +end +return dmg`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }], 0, 'number'), method('PlayCard', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then return end @@ -2428,7 +2468,7 @@ end self.Energy = self.Energy - c.cost if c.kind == "Attack" then if c.damage ~= nil then - self:PlayAttackFx(self.TargetIndex, c.image, c.damage) + self:PlayAttackFx(self.TargetIndex, c.image, self:CalcPlayerAttack(c.damage)) end if c.block ~= nil then self.PlayerBlock = self.PlayerBlock + c.block @@ -2438,9 +2478,25 @@ elseif c.kind == "Skill" then if c.block ~= nil then self.PlayerBlock = self.PlayerBlock + c.block end +elseif c.kind == "Power" then + if c.powerEffect ~= nil then + table.insert(self.PlayerPowers, cardId) + end +end +if c.strength ~= nil then + self.PlayerStr = self.PlayerStr + c.strength +end +if c.weak ~= nil or c.vuln ~= nil then + local tm = self.Monsters[self.TargetIndex] + if tm ~= nil and tm.alive == true then + if c.weak ~= nil then tm.weak = tm.weak + c.weak end + if c.vuln ~= nil then tm.vuln = tm.vuln + c.vuln end + end end table.remove(self.Hand, slot) -table.insert(self.DiscardPile, cardId) +if c.kind ~= "Power" then + table.insert(self.DiscardPile, cardId) +end self:RenderHand(false) self:RenderPiles() self:RenderCombat() @@ -2529,6 +2585,9 @@ if m == nil then return end local dmg = amount +if 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 @@ -2562,8 +2621,13 @@ end _TimerService:SetTimerOnce(function() if fx ~= nil then fx.Enable = false end self.FxBusy = false + local shown = damage + local mt = self.Monsters[targetIndex] + if mt ~= nil and mt.alive == true and mt.vuln > 0 then + shown = math.floor(damage * 1.5) + end self:DealDamageToTarget(damage) - self:ShowDmgPop(targetIndex, damage) + self:ShowDmgPop(targetIndex, shown) self:RenderCombat() self:CheckCombatEnd() end, 0.35)`, [ @@ -2612,17 +2676,32 @@ _TimerService:SetTimerOnce(function() local intent = m.intents[m.intentIdx] 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 local before = self.PlayerHp - self:DealDamageToPlayer(intent.value) + self:DealDamageToPlayer(atk) self:ShowPlayerDmgPop(before - self.PlayerHp) elseif intent.kind == "Defend" then m.block = m.block + intent.value + elseif intent.kind == "Debuff" then + if intent.effect == "weak" then + self.PlayerWeak = self.PlayerWeak + intent.value + elseif intent.effect == "vuln" then + self.PlayerVuln = self.PlayerVuln + intent.value + end end end m.intentIdx = m.intentIdx + 1 if m.intentIdx > #m.intents then m.intentIdx = 1 end + if m.weak > 0 then m.weak = m.weak - 1 end + if m.vuln > 0 then m.vuln = m.vuln - 1 end self:RenderCombat() self:SetEntityEnabled(base .. "/ActFrame", false) _TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15) From 00844857ee6ef618f9c633698b8c870dcbf3bc78 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:29:04 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat(buffs-power):=20=EC=A0=81=20=EB=B0=A9?= =?UTF-8?q?=EC=96=B4=EB=8F=84=20=EB=B0=B0=EC=A7=80=C2=B7=EB=B2=84=ED=94=84?= =?UTF-8?q?=20=EB=9D=BC=EC=9D=B8=C2=B7=EB=94=94=EB=B2=84=ED=94=84=20?= =?UTF-8?q?=EC=9D=B8=ED=85=90=ED=8A=B8=20UI=20(=EC=83=9D=EC=84=B1=EA=B8=B0?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MonsterSlot에 BlockBadge(방어도)·Buffs(힘/약화/취약) 추가 - PlayerPanel Buffs 라인 (버프 + 활성 파워 표시) - 인텐트: Debuff 보라색·공격 최종 예상치(힘·약화·취약 반영) 표시 Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/deck/gen-slaydeck.mjs | 78 ++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 4724d4f..f50c8f8 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -1019,6 +1019,37 @@ function upsertUi() { }); dmgPop.jsonString.enable = false; combat.push(dmgPop); + const mBlockBadge = entity({ + id: guid('cmb', 270 + i), path: `${base}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 6, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 36 }, pos: { x: -HP_BAR_W / 2 - 30, y: -14 } }), + sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }), + ], + }); + mBlockBadge.jsonString.enable = false; + combat.push(mBlockBadge); + combat.push(entity({ + id: guid('cmb', 280 + i), path: `${base}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 40, parentH: 36, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 32 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value: '0', fontSize: 17, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 290 + i), path: `${base}/Buffs`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 7, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 60, y: 22 }, pos: { x: 0, y: -58 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 15, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), + ], + })); } const PP = '/ui/DefaultGroup/CombatHud/PlayerPanel'; combat.push(entity({ @@ -1089,6 +1120,16 @@ function upsertUi() { text({ value: '0', fontSize: 18, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), ], })); + combat.push(entity({ + id: guid('cmb', 217), path: `${PP}/Buffs`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 6, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 22 }, pos: { x: 0, y: -44 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 14, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), + ], + })); const playerDmgPop = entity({ id: guid('cmb', 260), path: `${PP}/DmgPop`, modelId: 'uitext', entryId: 'UIText', componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', @@ -2765,6 +2806,15 @@ end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], N method('EndRun', `self:ShowResult(text) self.RunActive = false _TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]), + 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 +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' }, + ], 0, 'string'), method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) local m = self.Monsters[i] @@ -2775,8 +2825,16 @@ _TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)`, [{ Type: 'st local intent = m.intents[m.intentIdx] local t = "" if intent ~= nil then - if intent.kind == "Attack" then t = "공격 " .. tostring(intent.value) - elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value) end + 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 + end end self:SetText(base .. "/Intent", t) self:SetEntityEnabled(base .. "/TargetFrame", i == self.TargetIndex) @@ -2784,11 +2842,16 @@ _TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)`, [{ Type: 'st 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) 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)) else self:SetEntityEnabled(base, false) end @@ -2797,6 +2860,17 @@ self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/HpText", string.format("%d" self:SetHpBar("/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220) self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0) self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock)) +local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln) +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/DefaultGroup/CombatHud/PlayerPanel/Buffs", pb) self:RenderRun()`), method('ShowDmgPop', `local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot) .. "/DmgPop" self:SetText(base, "-" .. string.format("%d", amount)) From 0a96a6955a8fd00eb1649d2a40ffbf42742ac30c Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:32:31 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat(buffs-power):=20=EB=B0=B8=EB=9F=B0?= =?UTF-8?q?=EC=8A=A4=20=EC=8B=9C=EB=AE=AC=20=EB=B2=84=ED=94=84/=EB=94=94?= =?UTF-8?q?=EB=B2=84=ED=94=84=C2=B7Power=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - calcAttack(힘·약화·취약) 공식 export + 단위 테스트 - 적 Debuff 인텐트·플레이어/적 디버프 감소 타이밍 Lua 동기화 - Power 등록·매턴 발동·소멸 재현, chooseAction 파워 우선 - 신규 테스트 6건 (총 21건 통과) Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/balance/sim-balance.mjs | 70 ++++++++++++++++++++++++----- tools/balance/sim-balance.test.mjs | 71 +++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 13 deletions(-) diff --git a/tools/balance/sim-balance.mjs b/tools/balance/sim-balance.mjs index 209e970..f4fbb8e 100644 --- a/tools/balance/sim-balance.mjs +++ b/tools/balance/sim-balance.mjs @@ -27,6 +27,16 @@ export function shuffle(arr, rng) { return a; } +// 공격 피해 공식 — Lua CalcPlayerAttack(힘·약화) + DealDamageToTarget(취약)과 동기화. +// floor((base + str) * (weak>0 ? 0.75 : 1)) → floor(... * (vulnOnTarget>0 ? 1.5 : 1)) +export function calcAttack(base, str, weak, vulnOnTarget) { + let dmg = base + str; + if (weak > 0) dmg = Math.floor(dmg * 0.75); + if (vulnOnTarget > 0) dmg = Math.floor(dmg * 1.5); + if (dmg < 0) dmg = 0; + return dmg; +} + // 방어 우선 차감 후 hp 적용 → { hp, block } export function applyDamage(hp, block, amount) { let dmg = amount; @@ -54,14 +64,16 @@ export function loadData() { // 주의: 인게임은 플레이어가 카드를 직접 선택한다. 이 chooseAction은 밸런스 추정용 자동 플레이 휴리스틱일 뿐 // 이며, Lua에 대응 AI가 없다(동기화 대상은 데미지/방어/의도/승패 규칙이지 플레이어 선택이 아님). -// 손패에서 낼 카드 인덱스(-1=종료). 공격 우선, 없으면 스킬. +// 손패에서 낼 카드 인덱스(-1=종료). 파워 우선(지속 가치) → 공격 → 스킬. export function chooseAction(hand, cards, energy) { const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id].cost <= energy); + const powers = entries.filter((x) => cards[x.id].kind === 'Power'); const attacks = entries.filter((x) => cards[x.id].kind === 'Attack'); const skills = entries.filter((x) => cards[x.id].kind === 'Skill'); - const dmgEff = (x) => (cards[x.id].damage || 0) / cards[x.id].cost; - const blkEff = (x) => (cards[x.id].block || 0) / cards[x.id].cost; + const dmgEff = (x) => (cards[x.id].damage || 0) / Math.max(cards[x.id].cost, 1); + const blkEff = (x) => (cards[x.id].block || 0) / Math.max(cards[x.id].cost, 1); const bestBy = (list, fn) => list.slice().sort((a, b) => fn(b) - fn(a))[0]; + if (powers.length) return powers[0].i; if (attacks.length) return bestBy(attacks, dmgEff).i; if (skills.length) return bestBy(skills, blkEff).i; return -1; @@ -90,8 +102,10 @@ export function simulateCombat(data, rng, stats) { let discard = []; let hand = []; let pHp = PLAYER_HP, pBlock = 0; + let pStr = 0, pWeak = 0, pVuln = 0; + const powers = []; const mob = monsters.map((m) => ({ - name: m.name, hp: m.maxHp, maxHp: m.maxHp, block: 0, + name: m.name, hp: m.maxHp, maxHp: m.maxHp, block: 0, str: 0, weak: 0, vuln: 0, intents: m.intents, intentIdx: 0, alive: true, })); let turns = 0; @@ -107,6 +121,11 @@ export function simulateCombat(data, rng, stats) { while (turns < MAX_TURNS) { turns++; + // 파워 발동 — Lua StartPlayerTurn 동기화 (등록된 파워가 매턴 힘 누적) + for (const pid of powers) { + const pc = cards[pid]; + if (pc && pc.powerEffect === 'strengthPerTurn') pStr += pc.value; + } let energy = ENERGY; pBlock = 0; hand = []; draw(HAND_SIZE); while (true) { const alive = aliveList(); @@ -116,29 +135,56 @@ export function simulateCombat(data, rng, stats) { const id = hand[idx], c = cards[id]; energy -= c.cost; if (c.kind === 'Attack') { - const target = chooseTarget(alive, c.damage || 0); - const r = applyDamage(target.hp, target.block, c.damage || 0); + const target = chooseTarget(alive, calcAttack(c.damage || 0, pStr, pWeak, 0)); + // 카드 디버프는 피해보다 먼저 적용 — Lua PlayCard(즉시 부여) + 지연 데미지(0.35s) 동기화 + if (c.weak) target.weak += c.weak; + if (c.vuln) target.vuln += c.vuln; + const dmg = calcAttack(c.damage || 0, pStr, pWeak, target.vuln); + const r = applyDamage(target.hp, target.block, dmg); target.hp = r.hp; target.block = r.block; if (target.hp <= 0) target.alive = false; if (c.block) pBlock += c.block; - if (stats) stats[id] = bump(stats[id], c.cost, c.damage || 0, c.block || 0); + if (c.strength) pStr += c.strength; + if (stats) stats[id] = bump(stats[id], c.cost, dmg, c.block || 0); + } else if (c.kind === 'Power') { + if (c.powerEffect) powers.push(id); + if (stats) stats[id] = bump(stats[id], c.cost, 0, 0); } else { pBlock += c.block || 0; + if (c.strength) pStr += c.strength; + if (c.weak || c.vuln) { + const target = chooseTarget(alive, 0); + if (c.weak) target.weak += c.weak; + if (c.vuln) target.vuln += c.vuln; + } if (stats) stats[id] = bump(stats[id], c.cost, 0, c.block || 0); } - hand.splice(idx, 1); discard.push(id); + hand.splice(idx, 1); + if (c.kind !== 'Power') discard.push(id); // 파워는 소멸 — Lua 동기화 if (aliveList().length === 0) return { win: true, turns, playerHpRemaining: pHp }; } discard.push(...hand); hand = []; + // 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전) + if (pWeak > 0) pWeak--; + if (pVuln > 0) pVuln--; for (const m of mob) { if (!m.alive) continue; m.block = 0; // 매 턴 초기화 (이전 턴 블록 미이월) const it = m.intents[m.intentIdx]; if (it) { - if (it.kind === 'Attack') { const r = applyDamage(pHp, pBlock, it.value); pHp = r.hp; pBlock = r.block; } - else if (it.kind === 'Defend') { m.block += it.value; } + if (it.kind === 'Attack') { + const atk = calcAttack(it.value, m.str, m.weak, pVuln); + const r = applyDamage(pHp, pBlock, atk); pHp = r.hp; pBlock = r.block; + } else if (it.kind === 'Defend') { m.block += it.value; } + else if (it.kind === 'Debuff') { + if (it.effect === 'weak') pWeak += it.value; + else if (it.effect === 'vuln') pVuln += it.value; + } } m.intentIdx = (m.intentIdx + 1) % m.intents.length; + // 적 디버프 감소 — Lua EnemyActStep 동기화 (자기 행동 후) + if (m.weak > 0) m.weak--; + if (m.vuln > 0) m.vuln--; if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 }; } } @@ -188,11 +234,11 @@ export function formatReport(r) { const eff = kind === 'Attack' ? s.damage / s.energy : s.block / s.energy; return { id, name: r.cards[id].name, kind, plays: s.plays, eff }; }); - for (const kind of ['Attack', 'Skill']) { + for (const kind of ['Attack', 'Skill', 'Power']) { const kr = rows.filter((x) => x.kind === kind); if (!kr.length) continue; const med = median(kr.map((x) => x.eff)); - const unit = kind === 'Attack' ? '뎀/E' : '블록/E'; + const unit = kind === 'Attack' ? '뎀/E' : kind === 'Power' ? '(지속)' : '블록/E'; for (const x of kr) { const op = med > 0 && x.eff >= med * 1.5 ? ' ⚠️ OP 의심' : ''; L.push(` ${x.name}(${x.id}): 사용 ${x.plays}, 효율 ${x.eff.toFixed(2)} ${unit}${op}`); diff --git a/tools/balance/sim-balance.test.mjs b/tools/balance/sim-balance.test.mjs index 6401afd..a95fc88 100644 --- a/tools/balance/sim-balance.test.mjs +++ b/tools/balance/sim-balance.test.mjs @@ -1,7 +1,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { - mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, + mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, calcAttack, } from './sim-balance.mjs'; test('applyDamage: 방어 우선 차감 후 hp', () => { @@ -131,3 +131,72 @@ test('simulateCombat: 복합 카드(공격+방어) 블록이 적 공격을 흡 assert.equal(r.draw, true); assert.equal(r.playerHpRemaining, 80); }); + +test('calcAttack: 힘·약화·취약 공식 (Lua CalcPlayerAttack·DealDamageToTarget 동기화)', () => { + assert.equal(calcAttack(6, 0, 0, 0), 6); // 기본 + assert.equal(calcAttack(6, 2, 0, 0), 8); // 힘+2 + assert.equal(calcAttack(6, 0, 1, 0), 4); // 약화 floor(6*0.75) + assert.equal(calcAttack(6, 0, 0, 1), 9); // 취약 floor(6*1.5) + assert.equal(calcAttack(10, 2, 1, 1), 13); // floor(floor(12*0.75)=9 → floor(9*1.5))=13 +}); + +test('simulateCombat: 적 Debuff 인텐트만 사용 → 플레이어 무피해', () => { + const data = { + cards: { Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 1 } }, + starterDeck: ['Hit', 'Hit', 'Hit', 'Hit', 'Hit'], + monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Debuff', effect: 'weak', value: 1 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + assert.equal(r.playerHpRemaining, 80); +}); + +test('simulateCombat: 플레이어 약화 시 공격 피해 감소 반영', () => { + // 약화 영구 부여 적: 4피해 카드가 floor(4*0.75)=3으로 감소 + const data = { + cards: { Hit: { name: '타격', cost: 3, kind: 'Attack', damage: 4 } }, + starterDeck: ['Hit', 'Hit', 'Hit', 'Hit', 'Hit'], + monsters: [{ name: '적', maxHp: 10, intents: [{ kind: 'Debuff', effect: 'weak', value: 99 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + // 턴1: 4 (약화 전), 이후 매턴 3 → 10피해 도달 = 턴3 (4+3+3) + assert.equal(r.win, true); + assert.equal(r.turns, 3); +}); + +test('simulateCombat: 카드 취약 부여가 같은 카드 피해에 선적용 (Lua 동기화)', () => { + const data = { + cards: { CB: { name: '차지', cost: 3, kind: 'Attack', damage: 8, vuln: 2 } }, + starterDeck: ['CB', 'CB', 'CB', 'CB', 'CB'], + monsters: [{ name: '적', maxHp: 12, intents: [{ kind: 'Defend', value: 0 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + // 취약 선적용이면 floor(8*1.5)=12 → 1턴 처치. 후적용이면 8 → 2턴. + assert.equal(r.turns, 1); +}); + +test('simulateCombat: Power(매턴 힘) 누적', () => { + const data = { + cards: { + Rage: { name: '분노', cost: 1, kind: 'Power', powerEffect: 'strengthPerTurn', value: 5 }, + Hit: { name: '타격', cost: 1, kind: 'Attack', damage: 1 }, + }, + starterDeck: ['Rage', 'Hit', 'Hit', 'Hit', 'Hit'], + monsters: [{ name: '적', maxHp: 60, intents: [{ kind: 'Defend', value: 0 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + assert.equal(r.win, true); + assert.ok(r.turns <= 6, `파워 누적으로 빠른 처치 기대, 실제 ${r.turns}턴`); +}); + +test('simulateCombat: 적 약화 인텐트 → 적 공격력 감소는 적용 안 됨(적 자신은 약화 안 걸림)', () => { + // 회귀 가드: Debuff 인텐트는 플레이어에게만 적용 + const data = { + cards: { Skip: { name: '대기', cost: 3, kind: 'Skill', block: 0 } }, + starterDeck: ['Skip', 'Skip', 'Skip', 'Skip', 'Skip'], + monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Debuff', effect: 'vuln', value: 1 }, { kind: 'Attack', value: 10 }] }], + }; + const r = simulateCombat(data, mulberry32(1)); + // 턴1: 취약1 부여 → 플레이어 취약. 턴1 종료 시 1 감소 → 0. 턴2: 공격 10 (취약 소멸) → 정확히 10만 피해. + // MAX_TURNS 동안 2턴 주기 공격 → 사망까지 충분 → win=false + assert.equal(r.win, false); +}); From 88a913639815d16f1aae4313273a17ee4d073cde Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 01:33:20 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat(buffs-power):=20=EC=82=B0=EC=B6=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=9E=AC=EC=83=9D=EC=84=B1=20(=EB=B2=84=ED=94=84/?= =?UTF-8?q?=EB=94=94=EB=B2=84=ED=94=84=C2=B7Power=C2=B7=EC=A0=81=20?= =?UTF-8?q?=EB=B0=A9=EC=96=B4=EB=8F=84=20UI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- RootDesk/MyDesk/SlayDeckController.codeblock | 110 +- ui/DefaultGroup.ui | 2256 ++++++++++++++++++ 2 files changed, 2355 insertions(+), 11 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index c479e78..0d863d4 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -364,6 +364,34 @@ "SyncDirection": 0, "Attributes": [], "Name": "TurnBusy" + }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "PlayerStr" + }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "PlayerWeak" + }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "PlayerVuln" + }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "PlayerPowers" } ], "Methods": [ @@ -557,7 +585,7 @@ "Name": null }, "Arguments": [], - "Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 1\nself.RunLength = 3\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\nself.RunRelics = {}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6 },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1 },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1 },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10 },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\" }\nself.Enemies = {\n\tslime = { name = \"슬라임\", maxHp = 45, intents = { { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 8 } } },\n\tslime_elite = { name = \"정예 슬라임\", maxHp = 70, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Attack\", value = 8 }, { kind = \"Defend\", value = 10 } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n\torange_mushroom = { name = \"주황버섯\", maxHp = 16, intents = { { kind = \"Attack\", value = 5 }, { kind = \"Attack\", value = 5 }, { kind = \"Defend\", value = 4 }, { kind = \"Attack\", value = 8 } } },\n\tblue_mushroom = { name = \"파란버섯\", maxHp = 22, intents = { { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 10 } } },\n\tpig = { name = \"돼지\", maxHp = 18, intents = { { kind = \"Attack\", value = 6 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 5 } } },\n\tgreen_mushroom = { name = \"초록버섯\", maxHp = 20, intents = { { kind = \"Attack\", value = 7 }, { kind = \"Defend\", value = 3 }, { kind = \"Attack\", value = 9 } } },\n\tmushmom = { name = \"머쉬맘\", maxHp = 75, intents = { { kind = \"Defend\", value = 10 }, { kind = \"Attack\", value = 16 }, { kind = \"Attack\", value = 9 }, { kind = \"Defend\", value = 6 } } },\n\tmodified_snail = { name = \"변형된 달팽이\", maxHp = 60, intents = { { kind = \"Attack\", value = 12 }, { kind = \"Defend\", value = 8 }, { kind = \"Attack\", value = 7 }, { kind = \"Attack\", value = 14 } } },\n\tking_slime = { name = \"킹 슬라임\", maxHp = 130, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 14 }, { kind = \"Attack\", value = 12 }, { kind = \"Attack\", value = 24 } } },\n}\nself.MapNodes = {\n\tA = { type = \"combat\", enemy = \"slime\", row = 1, col = -1, next = { \"C\", \"D\" } },\n\tB = { type = \"combat\", enemy = \"slime\", row = 1, col = 1, next = { \"C\", \"D\" } },\n\tC = { type = \"rest\", row = 2, col = -1, next = { \"E\", \"F\" } },\n\tD = { type = \"shop\", row = 2, col = 1, next = { \"E\", \"F\" } },\n\tE = { type = \"elite\", enemy = \"slime_elite\", row = 3, col = -1, next = { \"BOSS\" } },\n\tF = { type = \"combat\", enemy = \"slime\", row = 3, col = 1, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 4, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:ShowMap()", + "Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 1\nself.RunLength = 3\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\nself.RunRelics = {}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6 },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1 },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1 },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10 },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\" }\nself.Enemies = {\n\tslime = { name = \"슬라임\", maxHp = 45, intents = { { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 8 } } },\n\tslime_elite = { name = \"정예 슬라임\", maxHp = 70, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Attack\", value = 8 }, { kind = \"Defend\", value = 10 }, { kind = \"Debuff\", value = 1, effect = \"weak\" } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Debuff\", value = 2, effect = \"vuln\" }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n\torange_mushroom = { name = \"주황버섯\", maxHp = 16, intents = { { kind = \"Attack\", value = 5 }, { kind = \"Attack\", value = 5 }, { kind = \"Defend\", value = 4 }, { kind = \"Attack\", value = 8 } } },\n\tblue_mushroom = { name = \"파란버섯\", maxHp = 22, intents = { { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 10 } } },\n\tpig = { name = \"돼지\", maxHp = 18, intents = { { kind = \"Attack\", value = 6 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 5 } } },\n\tgreen_mushroom = { name = \"초록버섯\", maxHp = 20, intents = { { kind = \"Attack\", value = 7 }, { kind = \"Defend\", value = 3 }, { kind = \"Attack\", value = 9 } } },\n\tmushmom = { name = \"머쉬맘\", maxHp = 75, intents = { { kind = \"Defend\", value = 10 }, { kind = \"Debuff\", value = 2, effect = \"weak\" }, { kind = \"Attack\", value = 16 }, { kind = \"Attack\", value = 9 }, { kind = \"Defend\", value = 6 } } },\n\tmodified_snail = { name = \"변형된 달팽이\", maxHp = 60, intents = { { kind = \"Attack\", value = 12 }, { kind = \"Defend\", value = 8 }, { kind = \"Attack\", value = 7 }, { kind = \"Attack\", value = 14 }, { kind = \"Debuff\", value = 1, effect = \"weak\" } } },\n\tking_slime = { name = \"킹 슬라임\", maxHp = 130, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 14 }, { kind = \"Debuff\", value = 2, effect = \"vuln\" }, { kind = \"Attack\", value = 12 }, { kind = \"Attack\", value = 24 } } },\n}\nself.MapNodes = {\n\tA = { type = \"combat\", enemy = \"slime\", row = 1, col = -1, next = { \"C\", \"D\" } },\n\tB = { type = \"combat\", enemy = \"slime\", row = 1, col = 1, next = { \"C\", \"D\" } },\n\tC = { type = \"rest\", row = 2, col = -1, next = { \"E\", \"F\" } },\n\tD = { type = \"shop\", row = 2, col = 1, next = { \"E\", \"F\" } },\n\tE = { type = \"elite\", enemy = \"slime_elite\", row = 3, col = -1, next = { \"BOSS\" } },\n\tF = { type = \"combat\", enemy = \"slime\", row = 3, col = 1, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 4, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:ShowMap()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -572,7 +600,7 @@ "Name": null }, "Arguments": [], - "Code": "self:ShowState(\"combat\")\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/Result\", false)\nself.MaxEnergy = 3\nself.Turn = 0\nself.PlayerBlock = 0\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"파워 스트라이크\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6, image = \"a71b116807904ef2b38e1dc013e2f9a2\" },\n\tDefend = { name = \"아이언 바디\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5, image = \"1ae9b6741c5947a8b528a0f515b50e3e\" },\n\tBash = { name = \"슬래시 블러스트\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10, image = \"d5bc2953fcab4cfe9062af81c35aff86\" },\n\tWarLeap = { name = \"워 리프\", cost = 1, desc = \"피해 4, 방어도 3\", kind = \"Attack\", damage = 4, block = 3, image = \"992dabf6aff2400e92b2f4f705d8ebe7\" },\n\tBrandish = { name = \"브랜디시\", cost = 2, desc = \"피해 13\", kind = \"Attack\", damage = 13, image = \"21af4bccc5054a5dbc8245dfa7f08681\" },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:BuildMonsters()\nself:RenderCombat()\nself:StartPlayerTurn()\nself:ApplyRelics(\"combatStart\")\nself:RenderCombat()", + "Code": "self:ShowState(\"combat\")\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/Result\", false)\nself.MaxEnergy = 3\nself.Turn = 0\nself.PlayerBlock = 0\nself.PlayerStr = 0\nself.PlayerWeak = 0\nself.PlayerVuln = 0\nself.PlayerPowers = {}\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"파워 스트라이크\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6, image = \"a71b116807904ef2b38e1dc013e2f9a2\" },\n\tDefend = { name = \"아이언 바디\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5, image = \"1ae9b6741c5947a8b528a0f515b50e3e\" },\n\tBash = { name = \"슬래시 블러스트\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10, image = \"d5bc2953fcab4cfe9062af81c35aff86\" },\n\tWarLeap = { name = \"워 리프\", cost = 1, desc = \"피해 4, 방어도 3\", kind = \"Attack\", damage = 4, block = 3, image = \"992dabf6aff2400e92b2f4f705d8ebe7\" },\n\tBrandish = { name = \"브랜디시\", cost = 2, desc = \"피해 13\", kind = \"Attack\", damage = 13, image = \"21af4bccc5054a5dbc8245dfa7f08681\" },\n\tChargedBlow = { name = \"차지 블로우\", cost = 2, desc = \"피해 8, 취약 2\", kind = \"Attack\", damage = 8, vuln = 2, image = \"fe83c7635b0e49ed83d75a2833adb53e\" },\n\tThreaten = { name = \"위협\", cost = 0, desc = \"약화 2 부여\", kind = \"Skill\", weak = 2, image = \"64daadf1a98e490d9c14ef52ec776e63\" },\n\tEnrage = { name = \"인레이지\", cost = 1, desc = \"힘 +2\", kind = \"Skill\", strength = 2, image = \"09370fc7551e47238fd103a80fba558e\" },\n\tRage = { name = \"분노\", cost = 1, desc = \"매 턴 시작 시 힘 +1\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, image = \"379d86e3de064959aa4612f71e84ccfb\" },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:BuildMonsters()\nself:RenderCombat()\nself:StartPlayerTurn()\nself:ApplyRelics(\"combatStart\")\nself:RenderCombat()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -631,7 +659,7 @@ "Name": null }, "Arguments": [], - "Code": "self.Monsters = {}\nlocal g = \"combat\"\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil and node.type ~= nil then g = node.type end\nlocal pmap = \"\"\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil and lp.CurrentMapName ~= nil then pmap = lp.CurrentMapName end\nlocal reg = self.Registered or {}\nfor i = 1, #reg do\n\tif reg[i].entity ~= nil and isvalid(reg[i].entity) then\n\t\treg[i].entity:SetVisible(false)\n\tend\nend\nlocal list = {}\nfor i = 1, #reg do\n\tlocal r = reg[i]\n\tif r.entity ~= nil and isvalid(r.entity) and r.group == g and (r.map == nil or r.map == \"\" or pmap == \"\" or r.map == pmap) then\n\t\tlocal x = 0\n\t\tif r.entity.TransformComponent ~= nil then\n\t\t\tx = r.entity.TransformComponent.WorldPosition.x\n\t\tend\n\t\ttable.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })\n\tend\nend\ntable.sort(list, function(a, b) return a.x < b.x end)\nlocal mult = 1 + (self.Floor - 1) * 0.6\nlocal n = #list\nif n > 4 then n = 4 end\nfor i = 1, n do\n\tlocal item = list[i]\n\tlocal e = self.Enemies[item.enemyId]\n\tif e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = \"Attack\", value = 5 } } } end\n\tlocal intents = {}\n\tfor k = 1, #e.intents do\n\t\tintents[k] = { kind = e.intents[k].kind, value = math.floor(e.intents[k].value * mult) }\n\tend\n\tlocal maxHp = math.floor(e.maxHp * mult)\n\tself.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,\n\t\thp = maxHp, maxHp = maxHp, block = 0, intents = intents, intentIdx = 1, alive = true, slot = i }\n\tself:ReviveMonsterEntity(item.entity)\n\tself:PositionMonsterSlot(i)\nend\nself.TargetIndex = 1", + "Code": "self.Monsters = {}\nlocal g = \"combat\"\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil and node.type ~= nil then g = node.type end\nlocal pmap = \"\"\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil and lp.CurrentMapName ~= nil then pmap = lp.CurrentMapName end\nlocal reg = self.Registered or {}\nfor i = 1, #reg do\n\tif reg[i].entity ~= nil and isvalid(reg[i].entity) then\n\t\treg[i].entity:SetVisible(false)\n\tend\nend\nlocal list = {}\nfor i = 1, #reg do\n\tlocal r = reg[i]\n\tif r.entity ~= nil and isvalid(r.entity) and r.group == g and (r.map == nil or r.map == \"\" or pmap == \"\" or r.map == pmap) then\n\t\tlocal x = 0\n\t\tif r.entity.TransformComponent ~= nil then\n\t\t\tx = r.entity.TransformComponent.WorldPosition.x\n\t\tend\n\t\ttable.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })\n\tend\nend\ntable.sort(list, function(a, b) return a.x < b.x end)\nlocal mult = 1 + (self.Floor - 1) * 0.6\nlocal n = #list\nif n > 4 then n = 4 end\nfor i = 1, n do\n\tlocal item = list[i]\n\tlocal e = self.Enemies[item.enemyId]\n\tif e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = \"Attack\", value = 5 } } } end\n\tlocal intents = {}\n\tfor k = 1, #e.intents do\n\t\tlocal v = e.intents[k].value\n\t\tif e.intents[k].kind ~= \"Debuff\" then\n\t\t\tv = math.floor(v * mult)\n\t\tend\n\t\tintents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect }\n\tend\n\tlocal maxHp = math.floor(e.maxHp * mult)\n\tself.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,\n\t\thp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0,\n\t\tintents = intents, intentIdx = 1, alive = true, slot = i }\n\tself:ReviveMonsterEntity(item.entity)\n\tself:PositionMonsterSlot(i)\nend\nself.TargetIndex = 1", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -707,7 +735,7 @@ "Name": null }, "Arguments": [], - "Code": "self.Turn = self.Turn + 1\nself.Energy = self.MaxEnergy\nself:ApplyRelics(\"turnStart\")\nself.PlayerBlock = 0\nself:DrawCards(5)\nself:RenderHand(true)\nself:RenderCombat()", + "Code": "self.Turn = self.Turn + 1\nself.Energy = self.MaxEnergy\nself:ApplyRelics(\"turnStart\")\nif self.PlayerPowers ~= nil then\n\tfor i = 1, #self.PlayerPowers do\n\t\tlocal pc = self.Cards[self.PlayerPowers[i]]\n\t\tif pc ~= nil and pc.powerEffect == \"strengthPerTurn\" then\n\t\t\tself.PlayerStr = self.PlayerStr + pc.value\n\t\tend\n\tend\nend\nself.PlayerBlock = 0\nself:DrawCards(5)\nself:RenderHand(true)\nself:RenderCombat()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -722,7 +750,7 @@ "Name": null }, "Arguments": [], - "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nfor i = 1, #self.Hand do\n\ttable.insert(self.DiscardPile, self.Hand[i])\nend\nself.Hand = {}\nself:RenderHand(false)\nself:RenderPiles()\nself:EnemyTurn()", + "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nfor i = 1, #self.Hand do\n\ttable.insert(self.DiscardPile, self.Hand[i])\nend\nself.Hand = {}\nif self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end\nif self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end\nself:RenderHand(false)\nself:RenderPiles()\nself:EnemyTurn()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1001,7 +1029,7 @@ "Name": "cardId" } ], - "Code": "local c = self.Cards[cardId]\nif c == nil then\n\tc = { name = cardId, cost = 0, desc = \"\", kind = \"Skill\" }\nend\nlocal e = _EntityService:GetEntityByPath(base)\nif e ~= nil and e.SpriteGUIRendererComponent ~= nil then\n\tif c.kind == \"Attack\" then\n\t\te.SpriteGUIRendererComponent.Color = Color(0.86, 0.42, 0.38, 1)\n\telseif c.kind == \"Skill\" then\n\t\te.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1)\n\telse\n\t\te.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1)\n\tend\nend\nself:SetText(base .. \"/Cost\", string.format(\"%d\", c.cost))\nself:SetText(base .. \"/Name\", c.name)\nself:SetText(base .. \"/Desc\", c.desc)\nlocal art = _EntityService:GetEntityByPath(base .. \"/Art\")\nif art ~= nil then\n\tif c.image ~= nil and c.image ~= \"\" then\n\t\tart.Enable = true\n\t\tif art.SpriteGUIRendererComponent ~= nil then\n\t\t\tart.SpriteGUIRendererComponent.ImageRUID = c.image\n\t\tend\n\telse\n\t\tart.Enable = false\n\tend\nend", + "Code": "local c = self.Cards[cardId]\nif c == nil then\n\tc = { name = cardId, cost = 0, desc = \"\", kind = \"Skill\" }\nend\nlocal e = _EntityService:GetEntityByPath(base)\nif e ~= nil and e.SpriteGUIRendererComponent ~= nil then\n\tif c.kind == \"Attack\" then\n\t\te.SpriteGUIRendererComponent.Color = Color(0.86, 0.42, 0.38, 1)\n\telseif c.kind == \"Power\" then\n\t\te.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1)\n\telse\n\t\te.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1)\n\tend\nend\nself:SetText(base .. \"/Cost\", string.format(\"%d\", c.cost))\nself:SetText(base .. \"/Name\", c.name)\nself:SetText(base .. \"/Desc\", c.desc)\nlocal art = _EntityService:GetEntityByPath(base .. \"/Art\")\nif art ~= nil then\n\tif c.image ~= nil and c.image ~= \"\" then\n\t\tart.Enable = true\n\t\tif art.SpriteGUIRendererComponent ~= nil then\n\t\t\tart.SpriteGUIRendererComponent.ImageRUID = c.image\n\t\tend\n\telse\n\t\tart.Enable = false\n\tend\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1111,6 +1139,29 @@ "Attributes": [], "Name": "AnimateCardFrom" }, + { + "Return": { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "base" + } + ], + "Code": "local dmg = base + self.PlayerStr\nif self.PlayerWeak > 0 then\n\tdmg = math.floor(dmg * 0.75)\nend\nif dmg < 0 then\n\tdmg = 0\nend\nreturn dmg", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "CalcPlayerAttack" + }, { "Return": { "Type": "void", @@ -1128,7 +1179,7 @@ "Name": "slot" } ], - "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil then\n\treturn\nend\nlocal cardId = self.Hand[slot]\nif cardId == nil then\n\treturn\nend\nlocal c = self.Cards[cardId]\nif c == nil then\n\treturn\nend\nif self.Energy < c.cost then\n\tself:Toast(\"에너지가 부족합니다\")\n\treturn\nend\nself.Energy = self.Energy - c.cost\nif c.kind == \"Attack\" then\n\tif c.damage ~= nil then\n\t\tself:PlayAttackFx(self.TargetIndex, c.image, c.damage)\n\tend\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\n\tself:ApplyRelics(\"cardPlayed\")\nelseif c.kind == \"Skill\" then\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\nend\ntable.remove(self.Hand, slot)\ntable.insert(self.DiscardPile, cardId)\nself:RenderHand(false)\nself:RenderPiles()\nself:RenderCombat()\nself:CheckCombatEnd()", + "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil then\n\treturn\nend\nlocal cardId = self.Hand[slot]\nif cardId == nil then\n\treturn\nend\nlocal c = self.Cards[cardId]\nif c == nil then\n\treturn\nend\nif self.Energy < c.cost then\n\tself:Toast(\"에너지가 부족합니다\")\n\treturn\nend\nself.Energy = self.Energy - c.cost\nif c.kind == \"Attack\" then\n\tif c.damage ~= nil then\n\t\tself:PlayAttackFx(self.TargetIndex, c.image, self:CalcPlayerAttack(c.damage))\n\tend\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\n\tself:ApplyRelics(\"cardPlayed\")\nelseif c.kind == \"Skill\" then\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\nelseif c.kind == \"Power\" then\n\tif c.powerEffect ~= nil then\n\t\ttable.insert(self.PlayerPowers, cardId)\n\tend\nend\nif c.strength ~= nil then\n\tself.PlayerStr = self.PlayerStr + c.strength\nend\nif c.weak ~= nil or c.vuln ~= nil then\n\tlocal tm = self.Monsters[self.TargetIndex]\n\tif tm ~= nil and tm.alive == true then\n\t\tif c.weak ~= nil then tm.weak = tm.weak + c.weak end\n\t\tif c.vuln ~= nil then tm.vuln = tm.vuln + c.vuln end\n\tend\nend\ntable.remove(self.Hand, slot)\nif c.kind ~= \"Power\" then\n\ttable.insert(self.DiscardPile, cardId)\nend\nself:RenderHand(false)\nself:RenderPiles()\nself:RenderCombat()\nself:CheckCombatEnd()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1287,7 +1338,7 @@ "Name": "amount" } ], - "Code": "local m = self.Monsters[self.TargetIndex]\nif m == nil or m.alive ~= true then\n\tm = nil\n\tfor i = 1, #self.Monsters do\n\t\tif self.Monsters[i].alive == true then m = self.Monsters[i]; self.TargetIndex = i; break end\n\tend\nend\nif m == nil then\n\treturn\nend\nlocal dmg = amount\nif m.block > 0 then\n\tlocal absorbed = math.min(m.block, dmg)\n\tm.block = m.block - absorbed\n\tdmg = dmg - absorbed\nend\nm.hp = m.hp - dmg\nif m.hp <= 0 then\n\tm.hp = 0\n\tself:KillMonster(m.slot)\nend", + "Code": "local m = self.Monsters[self.TargetIndex]\nif m == nil or m.alive ~= true then\n\tm = nil\n\tfor i = 1, #self.Monsters do\n\t\tif self.Monsters[i].alive == true then m = self.Monsters[i]; self.TargetIndex = i; break end\n\tend\nend\nif m == nil then\n\treturn\nend\nlocal dmg = amount\nif m.vuln > 0 then\n\tdmg = math.floor(dmg * 1.5)\nend\nif m.block > 0 then\n\tlocal absorbed = math.min(m.block, dmg)\n\tm.block = m.block - absorbed\n\tdmg = dmg - absorbed\nend\nm.hp = m.hp - dmg\nif m.hp <= 0 then\n\tm.hp = 0\n\tself:KillMonster(m.slot)\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1324,7 +1375,7 @@ "Name": "damage" } ], - "Code": "local m = self.Monsters[targetIndex]\nif m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then\n\tself:DealDamageToTarget(damage)\n\tself:RenderCombat()\n\tself:CheckCombatEnd()\n\treturn\nend\nself.FxBusy = true\nlocal fx = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/SkillFx\")\nif fx ~= nil then\n\tif fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= \"\" then\n\t\tfx.SpriteGUIRendererComponent.ImageRUID = image\n\tend\n\tif fx.UITransformComponent ~= nil and m.entity.TransformComponent ~= nil then\n\t\tlocal wp = m.entity.TransformComponent.WorldPosition\n\t\tlocal sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))\n\t\tfx.UITransformComponent.anchoredPosition = _UILogic:ScreenToUIPosition(sp)\n\tend\n\tfx.Enable = true\nend\n_TimerService:SetTimerOnce(function()\n\tif fx ~= nil then fx.Enable = false end\n\tself.FxBusy = false\n\tself:DealDamageToTarget(damage)\n\tself:ShowDmgPop(targetIndex, damage)\n\tself:RenderCombat()\n\tself:CheckCombatEnd()\nend, 0.35)", + "Code": "local m = self.Monsters[targetIndex]\nif m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then\n\tself:DealDamageToTarget(damage)\n\tself:RenderCombat()\n\tself:CheckCombatEnd()\n\treturn\nend\nself.FxBusy = true\nlocal fx = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/SkillFx\")\nif fx ~= nil then\n\tif fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= \"\" then\n\t\tfx.SpriteGUIRendererComponent.ImageRUID = image\n\tend\n\tif fx.UITransformComponent ~= nil and m.entity.TransformComponent ~= nil then\n\t\tlocal wp = m.entity.TransformComponent.WorldPosition\n\t\tlocal sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))\n\t\tfx.UITransformComponent.anchoredPosition = _UILogic:ScreenToUIPosition(sp)\n\tend\n\tfx.Enable = true\nend\n_TimerService:SetTimerOnce(function()\n\tif fx ~= nil then fx.Enable = false end\n\tself.FxBusy = false\n\tlocal shown = damage\n\tlocal mt = self.Monsters[targetIndex]\n\tif mt ~= nil and mt.alive == true and mt.vuln > 0 then\n\t\tshown = math.floor(damage * 1.5)\n\tend\n\tself:DealDamageToTarget(damage)\n\tself:ShowDmgPop(targetIndex, shown)\n\tself:RenderCombat()\n\tself:CheckCombatEnd()\nend, 0.35)", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1408,7 +1459,7 @@ "Name": "fromIndex" } ], - "Code": "local idx = 0\nfor i = fromIndex, #self.Monsters do\n\tif self.Monsters[i].alive == true then idx = i; break end\nend\nif idx == 0 or self.PlayerHp <= 0 then\n\tself:FinishEnemyTurn()\n\treturn\nend\nlocal m = self.Monsters[idx]\nlocal base = \"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(idx)\nself:SetEntityEnabled(base .. \"/ActFrame\", true)\n_TimerService:SetTimerOnce(function()\n\tm.block = 0\n\tlocal intent = m.intents[m.intentIdx]\n\tif intent ~= nil then\n\t\tif intent.kind == \"Attack\" then\n\t\t\tlocal before = self.PlayerHp\n\t\t\tself:DealDamageToPlayer(intent.value)\n\t\t\tself:ShowPlayerDmgPop(before - self.PlayerHp)\n\t\telseif intent.kind == \"Defend\" then\n\t\t\tm.block = m.block + intent.value\n\t\tend\n\tend\n\tm.intentIdx = m.intentIdx + 1\n\tif m.intentIdx > #m.intents then\n\t\tm.intentIdx = 1\n\tend\n\tself:RenderCombat()\n\tself:SetEntityEnabled(base .. \"/ActFrame\", false)\n\t_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)\nend, 0.45)", + "Code": "local idx = 0\nfor i = fromIndex, #self.Monsters do\n\tif self.Monsters[i].alive == true then idx = i; break end\nend\nif idx == 0 or self.PlayerHp <= 0 then\n\tself:FinishEnemyTurn()\n\treturn\nend\nlocal m = self.Monsters[idx]\nlocal base = \"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(idx)\nself:SetEntityEnabled(base .. \"/ActFrame\", true)\n_TimerService:SetTimerOnce(function()\n\tm.block = 0\n\tlocal intent = m.intents[m.intentIdx]\n\tif intent ~= nil then\n\t\tif intent.kind == \"Attack\" then\n\t\t\tlocal atk = intent.value + m.str\n\t\t\tif m.weak > 0 then\n\t\t\t\tatk = math.floor(atk * 0.75)\n\t\t\tend\n\t\t\tif self.PlayerVuln > 0 then\n\t\t\t\tatk = math.floor(atk * 1.5)\n\t\t\tend\n\t\t\tlocal before = self.PlayerHp\n\t\t\tself:DealDamageToPlayer(atk)\n\t\t\tself:ShowPlayerDmgPop(before - self.PlayerHp)\n\t\telseif intent.kind == \"Defend\" then\n\t\t\tm.block = m.block + intent.value\n\t\telseif intent.kind == \"Debuff\" then\n\t\t\tif intent.effect == \"weak\" then\n\t\t\t\tself.PlayerWeak = self.PlayerWeak + intent.value\n\t\t\telseif intent.effect == \"vuln\" then\n\t\t\t\tself.PlayerVuln = self.PlayerVuln + intent.value\n\t\t\tend\n\t\tend\n\tend\n\tm.intentIdx = m.intentIdx + 1\n\tif m.intentIdx > #m.intents then\n\t\tm.intentIdx = 1\n\tend\n\tif m.weak > 0 then m.weak = m.weak - 1 end\n\tif m.vuln > 0 then m.vuln = m.vuln - 1 end\n\tself:RenderCombat()\n\tself:SetEntityEnabled(base .. \"/ActFrame\", false)\n\t_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)\nend, 0.45)", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1505,6 +1556,43 @@ "Attributes": [], "Name": "EndRun" }, + { + "Return": { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "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" + } + ], + "Code": "local parts = {}\nif str ~= nil and str > 0 then table.insert(parts, \"힘+\" .. tostring(str)) end\nif weak ~= nil and weak > 0 then table.insert(parts, \"약화\" .. tostring(weak)) end\nif vuln ~= nil and vuln > 0 then table.insert(parts, \"취약\" .. tostring(vuln)) end\nreturn table.concat(parts, \" \")", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "BuffsLabel" + }, { "Return": { "Type": "void", @@ -1514,7 +1602,7 @@ "Name": null }, "Arguments": [], - "Code": "for i = 1, 4 do\n\tlocal base = \"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(i)\n\tlocal m = self.Monsters[i]\n\tif m ~= nil and m.alive == true then\n\t\tself:SetEntityEnabled(base, true)\n\t\tself:SetText(base .. \"/Name\", m.name)\n\t\tself:SetText(base .. \"/Hp\", string.format(\"%d\", m.hp) .. \"/\" .. string.format(\"%d\", m.maxHp))\n\t\tlocal intent = m.intents[m.intentIdx]\n\t\tlocal t = \"\"\n\t\tif intent ~= nil then\n\t\t\tif intent.kind == \"Attack\" then t = \"공격 \" .. tostring(intent.value)\n\t\t\telseif intent.kind == \"Defend\" then t = \"방어 \" .. tostring(intent.value) end\n\t\tend\n\t\tself:SetText(base .. \"/Intent\", t)\n\t\tself:SetEntityEnabled(base .. \"/TargetFrame\", i == self.TargetIndex)\n\t\tlocal intentEntity = _EntityService:GetEntityByPath(base .. \"/Intent\")\n\t\tif intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then\n\t\t\tif intent.kind == \"Attack\" then\n\t\t\t\tintentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)\n\t\t\telse\n\t\t\t\tintentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)\n\t\t\tend\n\t\tend\n\t\tself:SetHpBar(base .. \"/HpBarFill\", m.hp, m.maxHp, 140)\n\telse\n\t\tself:SetEntityEnabled(base, false)\n\tend\nend\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/HpText\", string.format(\"%d\", self.PlayerHp) .. \"/\" .. string.format(\"%d\", self.PlayerMaxHp))\nself:SetHpBar(\"/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill\", self.PlayerHp, self.PlayerMaxHp, 220)\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge\", self.PlayerBlock > 0)\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value\", string.format(\"%d\", self.PlayerBlock))\nself:RenderRun()", + "Code": "for i = 1, 4 do\n\tlocal base = \"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(i)\n\tlocal m = self.Monsters[i]\n\tif m ~= nil and m.alive == true then\n\t\tself:SetEntityEnabled(base, true)\n\t\tself:SetText(base .. \"/Name\", m.name)\n\t\tself:SetText(base .. \"/Hp\", string.format(\"%d\", m.hp) .. \"/\" .. string.format(\"%d\", m.maxHp))\n\t\tlocal intent = m.intents[m.intentIdx]\n\t\tlocal t = \"\"\n\t\tif intent ~= nil then\n\t\t\tif intent.kind == \"Attack\" then\n\t\t\t\tlocal atk = intent.value + m.str\n\t\t\t\tif m.weak > 0 then atk = math.floor(atk * 0.75) end\n\t\t\t\tif self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end\n\t\t\t\tt = \"공격 \" .. tostring(atk)\n\t\t\telseif intent.kind == \"Defend\" then t = \"방어 \" .. tostring(intent.value)\n\t\t\telseif intent.kind == \"Debuff\" then\n\t\t\t\tif intent.effect == \"weak\" then t = \"약화 \" .. tostring(intent.value) .. \" 부여\"\n\t\t\t\telse t = \"취약 \" .. tostring(intent.value) .. \" 부여\" end\n\t\t\tend\n\t\tend\n\t\tself:SetText(base .. \"/Intent\", t)\n\t\tself:SetEntityEnabled(base .. \"/TargetFrame\", i == self.TargetIndex)\n\t\tlocal intentEntity = _EntityService:GetEntityByPath(base .. \"/Intent\")\n\t\tif intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then\n\t\t\tif intent.kind == \"Attack\" then\n\t\t\t\tintentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)\n\t\t\telseif intent.kind == \"Debuff\" then\n\t\t\t\tintentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 1)\n\t\t\telse\n\t\t\t\tintentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)\n\t\t\tend\n\t\tend\n\t\tself:SetHpBar(base .. \"/HpBarFill\", m.hp, m.maxHp, 140)\n\t\tself:SetEntityEnabled(base .. \"/BlockBadge\", m.block > 0)\n\t\tself:SetText(base .. \"/BlockBadge/Value\", string.format(\"%d\", m.block))\n\t\tself:SetText(base .. \"/Buffs\", self:BuffsLabel(m.str, m.weak, m.vuln))\n\telse\n\t\tself:SetEntityEnabled(base, false)\n\tend\nend\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/HpText\", string.format(\"%d\", self.PlayerHp) .. \"/\" .. string.format(\"%d\", self.PlayerMaxHp))\nself:SetHpBar(\"/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill\", self.PlayerHp, self.PlayerMaxHp, 220)\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge\", self.PlayerBlock > 0)\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value\", string.format(\"%d\", self.PlayerBlock))\nlocal pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln)\nif self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then\n\tlocal names = {}\n\tfor i = 1, #self.PlayerPowers do\n\t\tlocal pc = self.Cards[self.PlayerPowers[i]]\n\t\tif pc ~= nil then table.insert(names, pc.name) end\n\tend\n\tif pb ~= \"\" then pb = pb .. \" · \" end\n\tpb = pb .. table.concat(names, \" \")\nend\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/Buffs\", pb)\nself:RenderRun()", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index 9e595fc..575f1ce 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -10767,6 +10767,523 @@ "@version": 1 } }, + { + "id": "0cb0010f-0000-4000-8000-00000cb0010f", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/BlockBadge", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "BlockBadge", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/BlockBadge", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 6, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UISprite", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uisprite", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": -80, + "y": 4 + }, + "OffsetMin": { + "x": -120, + "y": -32 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 36 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": -100, + "y": -14 + }, + "Position": { + "x": -100, + "y": -14, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0.32, + "g": 0.5, + "b": 0.85, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb00119-0000-4000-8000-00000cb00119", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/BlockBadge/Value", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Value", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/BlockBadge/Value", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 0, + "pathConstraints": "//////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 20, + "y": 16 + }, + "OffsetMin": { + "x": -20, + "y": -16 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 32 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 0 + }, + "Position": { + "x": 0, + "y": 0, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "FontSize": 17, + "MaxSize": 17, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "0", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb00123-0000-4000-8000-00000cb00123", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/Buffs", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Buffs", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/Buffs", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 7, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 100, + "y": -47 + }, + "OffsetMin": { + "x": -100, + "y": -69 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 200, + "y": 22 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -58 + }, + "Position": { + "x": 0, + "y": -58, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.85, + "g": 0.65, + "b": 1, + "a": 1 + }, + "FontSize": 15, + "MaxSize": 15, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0cb0002a-0000-4000-8000-00000cb0002a", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2", @@ -12271,6 +12788,523 @@ "@version": 1 } }, + { + "id": "0cb00110-0000-4000-8000-00000cb00110", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/BlockBadge", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "BlockBadge", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/BlockBadge", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 6, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UISprite", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uisprite", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": -80, + "y": 4 + }, + "OffsetMin": { + "x": -120, + "y": -32 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 36 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": -100, + "y": -14 + }, + "Position": { + "x": -100, + "y": -14, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0.32, + "g": 0.5, + "b": 0.85, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb0011a-0000-4000-8000-00000cb0011a", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/BlockBadge/Value", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Value", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/BlockBadge/Value", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 0, + "pathConstraints": "//////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 20, + "y": 16 + }, + "OffsetMin": { + "x": -20, + "y": -16 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 32 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 0 + }, + "Position": { + "x": 0, + "y": 0, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "FontSize": 17, + "MaxSize": 17, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "0", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb00124-0000-4000-8000-00000cb00124", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/Buffs", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Buffs", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/Buffs", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 7, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 100, + "y": -47 + }, + "OffsetMin": { + "x": -100, + "y": -69 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 200, + "y": 22 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -58 + }, + "Position": { + "x": 0, + "y": -58, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.85, + "g": 0.65, + "b": 1, + "a": 1 + }, + "FontSize": 15, + "MaxSize": 15, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0cb0002b-0000-4000-8000-00000cb0002b", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3", @@ -13775,6 +14809,523 @@ "@version": 1 } }, + { + "id": "0cb00111-0000-4000-8000-00000cb00111", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/BlockBadge", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "BlockBadge", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/BlockBadge", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 6, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UISprite", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uisprite", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": -80, + "y": 4 + }, + "OffsetMin": { + "x": -120, + "y": -32 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 36 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": -100, + "y": -14 + }, + "Position": { + "x": -100, + "y": -14, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0.32, + "g": 0.5, + "b": 0.85, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb0011b-0000-4000-8000-00000cb0011b", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/BlockBadge/Value", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Value", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/BlockBadge/Value", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 0, + "pathConstraints": "//////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 20, + "y": 16 + }, + "OffsetMin": { + "x": -20, + "y": -16 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 32 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 0 + }, + "Position": { + "x": 0, + "y": 0, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "FontSize": 17, + "MaxSize": 17, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "0", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb00125-0000-4000-8000-00000cb00125", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/Buffs", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Buffs", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/Buffs", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 7, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 100, + "y": -47 + }, + "OffsetMin": { + "x": -100, + "y": -69 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 200, + "y": 22 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -58 + }, + "Position": { + "x": 0, + "y": -58, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.85, + "g": 0.65, + "b": 1, + "a": 1 + }, + "FontSize": 15, + "MaxSize": 15, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0cb0002c-0000-4000-8000-00000cb0002c", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4", @@ -15279,6 +16830,523 @@ "@version": 1 } }, + { + "id": "0cb00112-0000-4000-8000-00000cb00112", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/BlockBadge", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "BlockBadge", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/BlockBadge", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 6, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UISprite", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uisprite", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": -80, + "y": 4 + }, + "OffsetMin": { + "x": -120, + "y": -32 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 36 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": -100, + "y": -14 + }, + "Position": { + "x": -100, + "y": -14, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0.32, + "g": 0.5, + "b": 0.85, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb0011c-0000-4000-8000-00000cb0011c", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/BlockBadge/Value", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Value", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/BlockBadge/Value", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 0, + "pathConstraints": "//////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 20, + "y": 16 + }, + "OffsetMin": { + "x": -20, + "y": -16 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 40, + "y": 32 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 0 + }, + "Position": { + "x": 0, + "y": 0, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "FontSize": 17, + "MaxSize": 17, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "0", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb00126-0000-4000-8000-00000cb00126", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/Buffs", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Buffs", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/Buffs", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 7, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 100, + "y": -47 + }, + "OffsetMin": { + "x": -100, + "y": -69 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 200, + "y": 22 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -58 + }, + "Position": { + "x": 0, + "y": -58, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.85, + "g": 0.65, + "b": 1, + "a": 1 + }, + "FontSize": 15, + "MaxSize": 15, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0cb000d2-0000-4000-8000-00000cb000d2", "path": "/ui/DefaultGroup/CombatHud/PlayerPanel", @@ -16407,6 +18475,194 @@ "@version": 1 } }, + { + "id": "0cb000d9-0000-4000-8000-00000cb000d9", + "path": "/ui/DefaultGroup/CombatHud/PlayerPanel/Buffs", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Buffs", + "path": "/ui/DefaultGroup/CombatHud/PlayerPanel/Buffs", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 6, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 140, + "y": -33 + }, + "OffsetMin": { + "x": -140, + "y": -55 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 280, + "y": 22 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -44 + }, + "Position": { + "x": 0, + "y": -44, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.85, + "g": 0.65, + "b": 1, + "a": 1 + }, + "FontSize": 14, + "MaxSize": 14, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0cb00104-0000-4000-8000-00000cb00104", "path": "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop",