From 858f9727dd1ab418296fa901e7de84c628a97ec5 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 03:18:10 +0900 Subject: [PATCH 1/6] =?UTF-8?q?docs(combat-feel):=20P3=20=EC=A0=84?= =?UTF-8?q?=ED=88=AC=20=EC=97=B0=EC=B6=9C=20=EC=84=A4=EA=B3=84+=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=20(=EB=93=9C=EB=9E=98=EA=B7=B8=20=ED=83=80=EA=B2=9F?= =?UTF-8?q?=C2=B7=EA=B3=B5=EA=B2=A9=20=EC=9D=B4=ED=8E=99=ED=8A=B8=C2=B7?= =?UTF-8?q?=EA=B0=9C=EB=B3=84=20=EC=B0=A8=EB=A1=80=C2=B7=ED=8C=9D=EC=97=85?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit probe 완료: ScreenTouchEvent/ScreenToUIPosition 실측, UITouchReceiveComponent 드래그 이벤트 확인. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-06-11-combat-feel.md | 289 ++++++++++++++++++ .../specs/2026-06-11-combat-feel-design.md | 63 ++++ 2 files changed, 352 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-11-combat-feel.md create mode 100644 docs/superpowers/specs/2026-06-11-combat-feel-design.md diff --git a/docs/superpowers/plans/2026-06-11-combat-feel.md b/docs/superpowers/plans/2026-06-11-combat-feel.md new file mode 100644 index 0000000..e9f77c4 --- /dev/null +++ b/docs/superpowers/plans/2026-06-11-combat-feel.md @@ -0,0 +1,289 @@ +# 전투 연출 (P3) 구현 계획 + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Task 6은 메이커 MCP — 컨트롤러 직접. + +**Goal:** 카드 드래그→몬스터 지정, 공격 이펙트 후 데미지, 적 개별 차례, 데미지 팝업. + +**Architecture:** 카드에 `UITouchReceiveComponent`(공식 드래그 이벤트). 연출은 컨트롤러 타이머 체인(`FxBusy`/`TurnBusy` 가드). 모든 변경은 `tools/deck/gen-slaydeck.mjs` 단일 소스. + +**Tech Stack:** Node ESM 생성기, MSW Lua, UITouchReceive/UILogic/TimerService. + +--- + +## 배경 (구현자용) +- 루트에서 `node tools/deck/gen-slaydeck.mjs`. 각 Task: `node --check` → 생성 → 확인 → **산출물 복원**(`git checkout -- ui/DefaultGroup.ui Global/common.gamelogic RootDesk/MyDesk/SlayDeckController.codeblock`) → 소스만 커밋. 산출물 커밋은 T5. +- 손패 카드: `/ui/DefaultGroup/CardHand/Card{1..5}`, 원위치 x=`CARD_XS[i]`(-400..400), y=0, 부모 CardHand는 화면 UI좌표 (0,-360) 중심(앵커 bottom-center pos y180). +- 몬스터: `self.Monsters[i] = {entity, ..., alive, slot}`; world→screen은 `_UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y+1.4))` 패턴(PositionMonsterSlot 참조). +- 기존: `BindButtons`에 카드 클릭 `ConnectEvent(ButtonClickEvent, ... PlayCard(i))` 루프 존재(제거 대상). `PlayCard`는 즉시 `DealDamageToTarget`. `EndPlayerTurn`은 손패 버림→`EnemyTurn()`→`CheckCombatEnd`→타이머로 `StartPlayerTurn`. `KillMonster`는 즉시 `SetVisible(false)`. +- guid 'cmb' 사용 대역: 0~10·41~144(+221~224 TargetFrame)·200~216. **신규: SkillFx=230, ActFrame=240+i, DmgPop slot=250+i, player DmgPop=260.** + +## Task 1: 카드 드래그 타겟팅 + +**Files:** Modify `tools/deck/gen-slaydeck.mjs` + +- [ ] **Step 1: 카드 엔티티에 UITouchReceiveComponent.** upsertUi 손패 카드(byPath 갱신 분기)에서 Card{i} 루트의 componentNames에 `MOD.Core.UITouchReceiveComponent`가 없으면 추가하고 `@components`에 `{ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }` push (기존 ButtonComponent 추가 패턴과 동일한 create-if-missing 방식 — 그 코드를 읽고 모방). + +- [ ] **Step 2: 드래그 상태 prop 추가.** SlayDeckController prop 배열에: +```js + prop('number', 'DragSlot', '0'), + prop('boolean', 'FxBusy', 'false'), + prop('boolean', 'TurnBusy', 'false'), +``` + +- [ ] **Step 3: BindButtons — 카드 클릭 제거 + 드래그 연결.** 기존 `for i = 1, 5 do ... PlayCard(i) ... end`(카드 ButtonClickEvent 루프)를 다음으로 교체: +```lua +for i = 1, 5 do + local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i)) + if cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then + cardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end) + cardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end) + cardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end) + end +end +``` + +- [ ] **Step 4: 드래그 메서드 3종 + ResolveCardDrop 추가** (CARD_XS는 JS 상수 — 보간으로 Lua 테이블 굽기): +```js + method('OnCardDragBegin', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then + return +end +if self.Hand == nil or self.Hand[slot] == nil then + return +end +self.DragSlot = slot`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), + method('OnCardDrag', `if self.DragSlot ~= slot then + return +end +local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot)) +if e ~= nil and e.UITransformComponent ~= nil then + local ui = _UILogic:ScreenToUIPosition(touchPoint) + e.UITransformComponent.anchoredPosition = Vector2(ui.x, ui.y + 360) +end`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }, + ]), + method('OnCardDragEnd', `if self.DragSlot ~= slot then + return +end +self.DragSlot = 0 +local cardXs = { ${CARD_XS.join(', ')} } +local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot)) +if e ~= nil and e.UITransformComponent ~= nil then + e.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0) +end +self:ResolveCardDrop(slot, touchPoint)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }, + ]), + method('ResolveCardDrop', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then + return +end +local cardId = self.Hand[slot] +if cardId == nil then + return +end +local c = self.Cards[cardId] +if c == nil then + return +end +if c.kind == "Attack" then + local best = 0 + local bestDist = 200 + for i = 1, #self.Monsters do + local m = self.Monsters[i] + if m.alive == true and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then + local wp = m.entity.TransformComponent.WorldPosition + local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7)) + local dx = sp.x - touchPoint.x + local dy = sp.y - touchPoint.y + local d = math.sqrt(dx * dx + dy * dy) + if d < bestDist then + bestDist = d + best = i + end + end + end + if best > 0 then + self.TargetIndex = best + self:PlayCard(slot) + end +else + local ui = _UILogic:ScreenToUIPosition(touchPoint) + if ui.y > -180 then + self:PlayCard(slot) + end +end`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }, + ]), +``` + +- [ ] **Step 5: 검증.** node --check → 생성 → `node -e "const cb=require('fs').readFileSync('RootDesk/MyDesk/SlayDeckController.codeblock','utf8');console.log('drag:',cb.includes('OnCardDragBegin')&&cb.includes('UITouchBeginDragEvent'),'| no card click PlayCard loop:',!/Card\\\" \.\. tostring\(i\)\)[\s\S]{0,220}ButtonClickEvent[\s\S]{0,80}PlayCard\(i\)/.test(cb))"` (두 번째 체크가 어려우면 수동으로 BindButtons에서 카드 ButtonClickEvent 루프 부재 확인). ui에 UITouchReceiveComponent 5장 확인: +`node -e "const ui=JSON.parse(require('fs').readFileSync('ui/DefaultGroup.ui','utf8'));console.log(ui.ContentProto.Entities.filter(e=>/CardHand\/Card[1-5]$/.test(e.path)&&e.componentNames.includes('UITouchReceiveComponent')).length)"` → 5. 산출물 복원. + +- [ ] **Step 6: Commit** `feat(combat-feel): 카드 드래그 타겟팅 (UITouchReceive·ResolveCardDrop)` + +## Task 2: 공격 이펙트 → 지연 데미지 + +- [ ] **Step 1: SkillFx 엔티티** (upsertUi CombatHud, Result 이전): +```js + const skillFx = entity({ + id: guid('cmb', 230), path: '/ui/DefaultGroup/CombatHud/SkillFx', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 30, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 110, y: 110 }, pos: { x: 0, y: 0 } }), + sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), + ], + }); + skillFx.jsonString.enable = false; + combat.push(skillFx); +``` + +- [ ] **Step 2: PlayCard Attack 분기 교체.** 기존: +``` +if c.kind == "Attack" then + if c.damage ~= nil then + self:DealDamageToTarget(c.damage) + end + self:ApplyRelics("cardPlayed") +``` +→ +``` +if c.kind == "Attack" then + if c.damage ~= nil then + self:PlayAttackFx(self.TargetIndex, c.image, c.damage) + end + self:ApplyRelics("cardPlayed") +``` +그리고 PlayCard 끝의 `self:CheckCombatEnd()`는 유지(Skill 경로용 — Attack은 PlayAttackFx 완료 시 재호출). + +- [ ] **Step 3: PlayAttackFx 추가:** +```js + method('PlayAttackFx', `local m = self.Monsters[targetIndex] +if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then + self:DealDamageToTarget(damage) + self:RenderCombat() + self:CheckCombatEnd() + return +end +self.FxBusy = true +local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx") +if fx ~= nil then + if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then + fx.SpriteGUIRendererComponent.ImageRUID = image + end + if fx.UITransformComponent ~= nil and m.entity.TransformComponent ~= nil then + local wp = m.entity.TransformComponent.WorldPosition + local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7)) + fx.UITransformComponent.anchoredPosition = _UILogic:ScreenToUIPosition(sp) + end + fx.Enable = true +end +_TimerService:SetTimerOnce(function() + if fx ~= nil then fx.Enable = false end + self.FxBusy = false + self:DealDamageToTarget(damage) + self:ShowDmgPop(targetIndex, damage) + self:RenderCombat() + self:CheckCombatEnd() +end, 0.35)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'targetIndex' }, + { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'image' }, + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'damage' }, + ]), +``` +주의: `ShowDmgPop`은 T3에서 추가 — T2 커밋 시점엔 생성기 실행이 깨지지 않음(문자열일 뿐). PlayCard/EndPlayerTurn 시작 가드에 `or self.FxBusy == true or self.TurnBusy == true` 추가(기존 `if self.CombatOver == true then return end`를 확장). + +- [ ] **Step 4: 검증** (codeblock에 PlayAttackFx·SkillFx 존재, PlayCard에 PlayAttackFx 호출). 산출물 복원, 커밋 `feat(combat-feel): 공격 이펙트 후 지연 데미지 (SkillFx·FxBusy)` + +## Task 3: 데미지 팝업 + 사망 지연 + +- [ ] **Step 1: DmgPop 엔티티.** 몬스터 슬롯 루프에 자식 추가(dOrder 9, guid cmb 250+i): 텍스트 120×30 @(0, 60), fontSize 24 bold, 색 {1,0.35,0.3,1}, value '', enable=false. PlayerPanel에도 동일(guid cmb 260, 경로 `PlayerPanel/DmgPop`, pos (16, 40), 색 {1,0.4,0.35,1}). +- [ ] **Step 2: ShowDmgPop / ShowPlayerDmgPop:** +```js + method('ShowDmgPop', `local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot) .. "/DmgPop" +self:SetText(base, "-" .. string.format("%d", amount)) +self:SetEntityEnabled(base, true) +_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, + ]), + method('ShowPlayerDmgPop', `local base = "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop" +if amount > 0 then + self:SetText(base, "-" .. string.format("%d", amount)) +else + self:SetText(base, "막음") +end +self:SetEntityEnabled(base, true) +_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), +``` +- [ ] **Step 3: KillMonster 사망 지연.** `m.entity:SetVisible(false)` 즉시 호출을: +``` +local ent = m.entity +_TimerService:SetTimerOnce(function() if isvalid(ent) then ent:SetVisible(false) end end, 0.4) +``` +로 교체. +- [ ] **Step 4: 검증·복원·커밋** `feat(combat-feel): 데미지 팝업·사망 지연` + +## Task 4: 적 개별 차례 + +- [ ] **Step 1: ActFrame 엔티티.** 몬스터 슬롯 루프에 자식(dOrder 0보다 아래는 불가하니 TargetFrame처럼 dOrder 0, guid cmb 240+i — TargetFrame과 별도): 156×108 @(0,0), 색 {0.95,0.3,0.25,0.3}, enable=false. (TargetFrame과 같은 위치 — 적 턴 중에는 TargetFrame 대신 표시됨.) +- [ ] **Step 2: EnemyTurn 시퀀스 교체.** `EnemyTurn` 전체를: +```js + method('EnemyTurn', `self.TurnBusy = true +self:EnemyActStep(1)`), + method('EnemyActStep', `local idx = 0 +for i = fromIndex, #self.Monsters do + if self.Monsters[i].alive == true then idx = i; break end +end +if idx == 0 or self.PlayerHp <= 0 then + self:FinishEnemyTurn() + return +end +local m = self.Monsters[idx] +local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(idx) +self:SetEntityEnabled(base .. "/ActFrame", true) +_TimerService:SetTimerOnce(function() + m.block = 0 + local intent = m.intents[m.intentIdx] + if intent ~= nil then + if intent.kind == "Attack" then + local before = self.PlayerHp + self:DealDamageToPlayer(intent.value) + self:ShowPlayerDmgPop(before - self.PlayerHp) + elseif intent.kind == "Defend" then + m.block = m.block + intent.value + end + end + m.intentIdx = m.intentIdx + 1 + if m.intentIdx > #m.intents then + m.intentIdx = 1 + end + self:RenderCombat() + self:SetEntityEnabled(base .. "/ActFrame", false) + _TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15) +end, 0.45)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromIndex' }]), + method('FinishEnemyTurn', `self.TurnBusy = false +self:CheckCombatEnd() +if self.CombatOver == true then + return +end +_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)`), +``` +- [ ] **Step 3: EndPlayerTurn 후반 정리.** 기존 EndPlayerTurn에서 `self:EnemyTurn()` 호출 이후의 `self:CheckCombatEnd()`/CombatOver 체크/`SetTimerOnce(... StartPlayerTurn ...)` 블록 **삭제**(FinishEnemyTurn이 담당). 가드 확장 확인(T2에서 FxBusy/TurnBusy). +- [ ] **Step 4: RenderCombat의 ActFrame 정리.** RenderCombat 몬스터 루프의 else(사망/없음) 분기는 슬롯 통째 비활성이므로 ActFrame 잔존 위험 없음 — 확인만. +- [ ] **Step 5: 검증·복원·커밋** `feat(combat-feel): 적 개별 차례 시퀀스 (ActFrame·EnemyActStep)` + +## Task 5: 재생성·검증·산출물 커밋 +P2 T5와 동일 절차(생성→JSON·dup0·핵심 심볼(OnCardDragBegin/PlayAttackFx/EnemyActStep/DmgPop)·결정성·sim 14/14→산출물 커밋 `feat(combat-feel): 산출물 재생성`). + +## Task 6 (컨트롤러 직접): 메이커 검증 + 푸시 + PR + 머지 +- mouse_input 드래그로: 공격 카드→몬스터2 드롭(타겟 변경+이펙트+팝업+HP 감소), Skill 카드 위로 드롭(방어), 빈 곳 드롭 취소, 턴 종료→순차 행동(ActFrame)+플레이어 팝업, 전체 처치 승리. 스크린샷 evidence. +- 푸시→Gitea API PR(상세 메시지)→머지. + +## Self-Review +- 요구 4종(드래그/모션 후 데미지/개별 차례/팝업·사망) ↔ T1/T2/T4/T3 매핑 완료. ResolveCardDrop의 `TargetIndex` 직접 대입은 SetTarget(RenderCombat 포함)과 달리 렌더 없이 PlayCard로 직행 — PlayCard가 RenderCombat 수행하므로 OK. +- 시그니처 일관: PlayAttackFx(targetIndex,image,damage)·ShowDmgPop(slot,amount)·EnemyActStep(fromIndex). guid 230/240+i/250+i/260 비충돌(기존 0~224). +- 리스크는 T6 메이커 검증에서 흡수(드래그 좌표 보정 +360, 거리 임계 200, ui.y>-180 스윕 기준 — 실측 튜닝 가능). diff --git a/docs/superpowers/specs/2026-06-11-combat-feel-design.md b/docs/superpowers/specs/2026-06-11-combat-feel-design.md new file mode 100644 index 0000000..520c553 --- /dev/null +++ b/docs/superpowers/specs/2026-06-11-combat-feel-design.md @@ -0,0 +1,63 @@ +# 전투 연출 (P3) — 설계 + +- 날짜: 2026-06-11 +- 대상: `tools/deck/gen-slaydeck.mjs`(카드 드래그·연출·적 턴 시퀀스), 생성물 +- 상태: 승인됨(사용자 사전 위임 — P2~P5 일괄 진행 지시). 로드맵 P3/5. + +## 1. 목표 (사용자 요구) + +1. **카드 드래그→몬스터 지정**: 카드를 끌어 특정 몬스터에 놓아 사용(클릭 대체). +2. **공격 모션 후 데미지**: 공격 카드 사용 시 연출(스킬 이펙트) 후 몬스터가 피해. +3. **몬스터 개별 차례**: 적 턴에 몬스터가 한 마리씩 순서대로 행동(행동자 표시). +4. 데미지 숫자 표시·사망 연출 등 게임필 보강. + +## 2. 타당성 (probe 완료) + +- `maker_mouse_input` down → `ScreenTouchEvent` 발화 + `ScreenToUIPosition` 변환 실측 일치(카드2 위치). +- **`MOD.Core.UITouchReceiveComponent`**: UI 엔티티에 부착 시 `UITouchBeginDragEvent`/`UITouchDragEvent`/`UITouchEndDragEvent`/`UITouchDownEvent`/`UITouchUpEvent` 제공(공식, Client). 드래그는 이걸 사용. +- 몬스터 world→screen(`_UILogic:WorldToScreenPosition`)은 P1에서 검증됨 — 드롭 판정에 재사용. + +## 3. 설계 + +### 3.1 카드 드래그 타겟팅 +- 손패 Card1~5 엔티티에 `MOD.Core.UITouchReceiveComponent` 추가(생성기 componentNames+컴포넌트). +- 컨트롤러 상태: `DragSlot`(0=없음), `DragOrigin`(원위치 Vector2), `DragMoved`(boolean). +- `BindButtons`에서 카드별로 connect: + - `UITouchBeginDragEvent` → CombatOver 아니고 손패에 카드 있으면 `DragSlot=i`, 원위치 저장(`CARD_XS[i]` 상수로 복원 가능하므로 저장은 단순화 가능 — 원위치 = (CARD_XS[i], 0)). + - `UITouchDragEvent` → 카드 `anchoredPosition = ScreenToUIPosition(TouchPoint) - CardHandOffset` (CardHand 부모 중심의 UI 좌표 보정값은 런타임 계산: 카드 부모 CardHand의 화면상 중심 = UI(0, -360) → 보정 상수로 굽기). + - `UITouchEndDragEvent` → `ResolveCardDrop(i, TouchPoint)` 후 카드 위치 복원. +- `ResolveCardDrop(slot, screenPoint)`: + - 카드 kind 조회. **Attack**: 생존 몬스터 중 화면 거리(몬스터 world→screen vs screenPoint) 최소이고 임계(예: 200px) 이내인 몬스터 → `SetTarget(그 몬스터)` 후 `PlayCard(slot)`. 임계 밖이면 취소(복귀만). + - **Skill**: 드롭 위치가 손패 위(화면 y 기준 카드 영역 위쪽, 예: screen y > 화면 40%)면 `PlayCard(slot)`, 아니면 취소. +- 기존 카드 ButtonComponent 클릭 `PlayCard` 바인딩 **제거**(드래그와 충돌 방지, 사용은 드래그로 일원화 — STS 방식). 몬스터 슬롯 클릭 SetTarget은 유지(타겟만 바꾸는 보조 수단). + +### 3.2 공격 연출 → 데미지 (PlayCard Attack 시퀀스) +- `CombatHud/SkillFx` 엔티티 1개(96×96 이미지 스프라이트, 평소 숨김). +- PlayCard(Attack) 흐름 변경: 에너지 차감·손패 제거·렌더는 즉시, **데미지는 지연**: + 1. `ShowSkillFx(targetIndex, c.image)`: 타겟 몬스터 world→screen 위치에 SkillFx 표시(ImageRUID=카드 이미지). + 2. 0.35s 타이머 → SkillFx 숨김 + `DealDamageToTarget(damage)` + 데미지 팝업 + RenderCombat + CheckCombatEnd. +- 연출 중 입력 보호: `FxBusy=true` 동안 PlayCard/EndPlayerTurn 무시(0.35s). + +### 3.3 데미지 숫자 팝업 +- `MonsterSlot{i}/DmgPop`(텍스트, 숨김 기본): `ShowDmgPop(slot, amount)` — "-N" 표시 → 0.6s 후 숨김(타이머; 위치 고정 단순화). +- `PlayerPanel/DmgPop` 동일(적 공격 시 "-N", 방어 흡수로 0이면 "막음"). + +### 3.4 적 개별 차례 (EnemyTurn 시퀀스화) +- `EnemyTurn` → 비동기 체인으로 재작성: + - `EnemyActIndex=0`; `EnemyActStep()`: 다음 생존 몬스터 찾기 → 없으면 `FinishEnemyTurn()`. + - 행동 몬스터 슬롯에 `ActFrame`(적색 하이라이트 — TargetFrame과 별도 자식, 156×108 적색 a0.3) 표시 → 0.45s 타이머 → 의도 적용(Attack: DealDamageToPlayer+플레이어 DmgPop / Defend: block+슬롯 의도 갱신) → ActFrame 숨김 → 다음 `EnemyActStep()` (0.15s 간격). + - 플레이어 사망 시 즉시 `FinishEnemyTurn()`. +- `FinishEnemyTurn()`: `CheckCombatEnd` 후 미종료면 0.45s 뒤 `StartPlayerTurn`(기존 EndPlayerTurn 후반부 이동). +- `EndPlayerTurn`: 손패 버림+렌더 후 `EnemyTurn()` 호출로 종료(후속 로직은 FinishEnemyTurn으로 이동). `TurnBusy=true`로 적 턴 중 입력 차단(FxBusy와 함께 가드). + +### 3.5 사망 연출 +- `KillMonster`: 즉시 SetVisible(false) → **0.4s 지연**으로 변경(DmgPop과 겹쳐 보이게), 슬롯 비활성은 즉시 유지. + +## 4. 검증 +- 생성 결정성·dup 0·sim 14/14(규칙 불변 — 연출 지연만 추가, 데미지 계산 동일). +- 메이커: ①카드를 몬스터2에 드래그→타겟 변경+이펙트→데미지 팝업→HP 감소 ②Skill 카드 위로 드래그→방어 ③드롭 취소(빈 곳) ④턴 종료→적들이 한 마리씩 순차 행동(ActFrame 이동)+플레이어 팝업 ⑤전체 처치 승리 정상. + +## 5. 리스크 +- UITouchReceiveComponent와 ButtonComponent 공존(슬롯 클릭/드래그 간섭) — 카드에서 Button 제거하므로 카드는 안전; 몬스터 슬롯은 Button 유지(드래그 없음). +- UITouchDragEvent 빈도/좌표계 — 구현 후 메이커 검증(§4①). 드래그 좌표 보정 상수는 실측 튜닝. +- 비동기 체인 중 상태 변화(연출 중 사망 등) — FxBusy/TurnBusy 가드 + 각 스텝에서 alive/CombatOver 재확인. -- 2.49.1 From c80020a464585d27613f99f08c4b4ae3596912d9 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 03:20:33 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat(combat-feel):=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EB=93=9C=EB=9E=98=EA=B7=B8=20=ED=83=80=EA=B2=9F=ED=8C=85=20(UI?= =?UTF-8?q?TouchReceive=C2=B7ResolveCardDrop)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- tools/deck/gen-slaydeck.mjs | 87 ++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 9642ee9..0953cde 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -419,6 +419,12 @@ function upsertUi() { if (!card.componentNames.includes('MOD.Core.ButtonComponent')) { card.componentNames += ',MOD.Core.ButtonComponent'; } + if (!comps.some((c) => c['@type'] === 'MOD.Core.UITouchReceiveComponent')) { + comps.push({ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }); + } + if (!card.componentNames.includes('MOD.Core.UITouchReceiveComponent')) { + card.componentNames += ',MOD.Core.UITouchReceiveComponent'; + } card.jsonString.enable = true; card.jsonString.visible = true; @@ -1831,6 +1837,9 @@ function writeCodeblocks() { prop('any', 'RelicPool'), prop('string', 'ShopRelic', '""'), prop('boolean', 'ShopRelicBought', 'false'), + prop('number', 'DragSlot', '0'), + prop('boolean', 'FxBusy', 'false'), + prop('boolean', 'TurnBusy', 'false'), ], [ method('OnBeginPlay', `self:ShowMainMenu()`), method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false) @@ -2070,8 +2079,10 @@ if allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then end for i = 1, 5 do local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i)) - if cardEntity ~= nil and cardEntity.ButtonComponent ~= nil then - cardEntity:ConnectEvent(ButtonClickEvent, function() self:PlayCard(i) end) + if cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then + cardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end) + cardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end) + cardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end) end end for i = 1, 3 do @@ -2382,6 +2393,78 @@ self:RenderHand(false) self:RenderPiles() self:RenderCombat() self:CheckCombatEnd()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), + method('OnCardDragBegin', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then + return +end +if self.Hand == nil or self.Hand[slot] == nil then + return +end +self.DragSlot = slot`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), + method('OnCardDrag', `if self.DragSlot ~= slot then + return +end +local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot)) +if e ~= nil and e.UITransformComponent ~= nil then + local ui = _UILogic:ScreenToUIPosition(touchPoint) + e.UITransformComponent.anchoredPosition = Vector2(ui.x, ui.y + 360) +end`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }, + ]), + method('OnCardDragEnd', `if self.DragSlot ~= slot then + return +end +self.DragSlot = 0 +local cardXs = { ${CARD_XS.join(', ')} } +local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot)) +if e ~= nil and e.UITransformComponent ~= nil then + e.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0) +end +self:ResolveCardDrop(slot, touchPoint)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }, + ]), + method('ResolveCardDrop', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then + return +end +local cardId = self.Hand[slot] +if cardId == nil then + return +end +local c = self.Cards[cardId] +if c == nil then + return +end +if c.kind == "Attack" then + local best = 0 + local bestDist = 200 + for i = 1, #self.Monsters do + local m = self.Monsters[i] + if m.alive == true and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then + local wp = m.entity.TransformComponent.WorldPosition + local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7)) + local dx = sp.x - touchPoint.x + local dy = sp.y - touchPoint.y + local d = math.sqrt(dx * dx + dy * dy) + if d < bestDist then + bestDist = d + best = i + end + end + end + if best > 0 then + self.TargetIndex = best + self:PlayCard(slot) + end +else + local ui = _UILogic:ScreenToUIPosition(touchPoint) + if ui.y > -180 then + self:PlayCard(slot) + end +end`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' }, + ]), method('Toast', `log(message)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'message' }]), method('DealDamageToTarget', `local m = self.Monsters[self.TargetIndex] if m == nil or m.alive ~= true then -- 2.49.1 From 4b7ad753a48faae4a471076739f84814ccd476f8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 08:25:05 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat(combat-feel):=20=EA=B3=B5=EA=B2=A9=20?= =?UTF-8?q?=EC=9D=B4=ED=8E=99=ED=8A=B8=20=ED=9B=84=20=EC=A7=80=EC=97=B0=20?= =?UTF-8?q?=EB=8D=B0=EB=AF=B8=EC=A7=80=20(SkillFx=C2=B7FxBusy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- tools/deck/gen-slaydeck.mjs | 50 ++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 0953cde..fadc526 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -1100,6 +1100,18 @@ function upsertUi() { text({ value: '모든덱보기', fontSize: 18, bold: true, color: GOLD, alignment: 0 }), ], })); + const skillFx = entity({ + id: guid('cmb', 230), path: '/ui/DefaultGroup/CombatHud/SkillFx', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 30, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 110, y: 110 }, pos: { x: 0, y: 0 } }), + sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), + ], + }); + skillFx.jsonString.enable = false; + combat.push(skillFx); const result = entity({ id: guid('cmb', 2), path: '/ui/DefaultGroup/CombatHud/Result', @@ -2134,7 +2146,7 @@ self.PlayerBlock = 0 self:DrawCards(5) self:RenderHand(true) self:RenderCombat()`), - method('EndPlayerTurn', `if self.CombatOver == true then + method('EndPlayerTurn', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then return end for i = 1, #self.Hand do @@ -2358,7 +2370,7 @@ end, 1 / 60)`, [ { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toPos' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'duration' }, ]), - method('PlayCard', `if self.CombatOver == true then + method('PlayCard', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then return end if self.Hand == nil then @@ -2379,7 +2391,7 @@ end self.Energy = self.Energy - c.cost if c.kind == "Attack" then if c.damage ~= nil then - self:DealDamageToTarget(c.damage) + self:PlayAttackFx(self.TargetIndex, c.image, c.damage) end self:ApplyRelics("cardPlayed") elseif c.kind == "Skill" then @@ -2487,6 +2499,38 @@ if m.hp <= 0 then m.hp = 0 self:KillMonster(m.slot) end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), + method('PlayAttackFx', `local m = self.Monsters[targetIndex] +if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then + self:DealDamageToTarget(damage) + self:RenderCombat() + self:CheckCombatEnd() + return +end +self.FxBusy = true +local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx") +if fx ~= nil then + if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then + fx.SpriteGUIRendererComponent.ImageRUID = image + end + if fx.UITransformComponent ~= nil and m.entity.TransformComponent ~= nil then + local wp = m.entity.TransformComponent.WorldPosition + local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7)) + fx.UITransformComponent.anchoredPosition = _UILogic:ScreenToUIPosition(sp) + end + fx.Enable = true +end +_TimerService:SetTimerOnce(function() + if fx ~= nil then fx.Enable = false end + self.FxBusy = false + self:DealDamageToTarget(damage) + self:ShowDmgPop(targetIndex, damage) + self:RenderCombat() + self:CheckCombatEnd() +end, 0.35)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'targetIndex' }, + { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'image' }, + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'damage' }, + ]), method('KillMonster', `local m = self.Monsters[slot] if m == nil then return -- 2.49.1 From 5921dfbeb84896d416a320c70c5b716edc31733b Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 08:28:02 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat(combat-feel):=20=EB=8D=B0=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=8C=9D=EC=97=85=C2=B7=EC=82=AC=EB=A7=9D=20?= =?UTF-8?q?=EC=A7=80=EC=97=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- tools/deck/gen-slaydeck.mjs | 42 ++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index fadc526..e753f6b 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -987,6 +987,18 @@ function upsertUi() { text({ value: '', fontSize: 17, bold: true, color: { r: 1, g: 0.72, b: 0.5, a: 1 }, alignment: 4 }), ], })); + const dmgPop = entity({ + id: guid('cmb', 250 + i), path: `${base}/DmgPop`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 9, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 120, y: 30 }, pos: { x: 0, y: 60 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 24, bold: true, color: { r: 1, g: 0.35, b: 0.3, a: 1 }, alignment: 4 }), + ], + }); + dmgPop.jsonString.enable = false; + combat.push(dmgPop); } const PP = '/ui/DefaultGroup/CombatHud/PlayerPanel'; combat.push(entity({ @@ -1057,6 +1069,18 @@ function upsertUi() { text({ value: '0', fontSize: 18, bold: true, color: { r: 1, g: 1, 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', + displayOrder: 5, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 160, y: 30 }, pos: { x: 16, y: 40 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 22, bold: true, color: { r: 1, g: 0.4, b: 0.35, a: 1 }, alignment: 4 }), + ], + }); + playerDmgPop.jsonString.enable = false; + combat.push(playerDmgPop); combat.push(entity({ id: guid('cmb', 200), path: '/ui/DefaultGroup/CombatHud/TopBar', @@ -2537,7 +2561,8 @@ if m == nil then end m.alive = false if m.entity ~= nil and isvalid(m.entity) then - m.entity:SetVisible(false) + local ent = m.entity + _TimerService:SetTimerOnce(function() if isvalid(ent) then ent:SetVisible(false) end end, 0.4) end self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot), false) for i = 1, #self.Monsters do @@ -2642,6 +2667,21 @@ self:SetHpBar("/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0) self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock)) self:RenderRun()`), + method('ShowDmgPop', `local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot) .. "/DmgPop" +self:SetText(base, "-" .. string.format("%d", amount)) +self:SetEntityEnabled(base, true) +_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, + ]), + method('ShowPlayerDmgPop', `local base = "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop" +if amount > 0 then + self:SetText(base, "-" .. string.format("%d", amount)) +else + self:SetText(base, "막음") +end +self:SetEntityEnabled(base, true) +_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), method('SetHpBar', `local e = _EntityService:GetEntityByPath(path) if e == nil or e.UITransformComponent == nil then return -- 2.49.1 From f88e6ffeebd6525fe7cf1beddd179d0020abb9c8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 08:31:37 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat(combat-feel):=20=EC=A0=81=20=EA=B0=9C?= =?UTF-8?q?=EB=B3=84=20=EC=B0=A8=EB=A1=80=20=EC=8B=9C=ED=80=80=EC=8A=A4=20?= =?UTF-8?q?(ActFrame=C2=B7EnemyActStep)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- tools/deck/gen-slaydeck.mjs | 70 +++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index e753f6b..2632b78 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -939,6 +939,17 @@ function upsertUi() { }); targetFrame.jsonString.enable = false; combat.push(targetFrame); + const actFrame = entity({ + id: guid('cmb', 240 + i), path: `${base}/ActFrame`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + 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 + 16, y: SLOT_H + 12 }, pos: { x: 0, y: 0 } }), + sprite({ color: { r: 0.95, g: 0.3, b: 0.25, a: 0.3 }, type: 1 }), + ], + }); + actFrame.jsonString.enable = false; + combat.push(actFrame); combat.push(entity({ id: guid('cmb', 60 + i), path: `${base}/Name`, modelId: 'uitext', entryId: 'UIText', componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', @@ -2179,12 +2190,7 @@ end self.Hand = {} self:RenderHand(false) self:RenderPiles() -self:EnemyTurn() -self:CheckCombatEnd() -if self.CombatOver == true then - return -end -_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)`), +self:EnemyTurn()`), method('DrawCards', `for i = 1, amount do \tif #self.DrawPile <= 0 then \t\tself:RecycleDiscardIntoDraw() @@ -2578,25 +2584,45 @@ self.PlayerHp = self.PlayerHp - dmg if self.PlayerHp < 0 then self.PlayerHp = 0 end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), - method('EnemyTurn', `for i = 1, #self.Monsters do - local m = self.Monsters[i] - if m.alive == true then - m.block = 0 - local intent = m.intents[m.intentIdx] - if intent ~= nil then - if intent.kind == "Attack" then - self:DealDamageToPlayer(intent.value) - elseif intent.kind == "Defend" then - m.block = m.block + intent.value - end - end - m.intentIdx = m.intentIdx + 1 - if m.intentIdx > #m.intents then - m.intentIdx = 1 + method('EnemyTurn', `self.TurnBusy = true +self:EnemyActStep(1)`), + method('EnemyActStep', `local idx = 0 +for i = fromIndex, #self.Monsters do + if self.Monsters[i].alive == true then idx = i; break end +end +if idx == 0 or self.PlayerHp <= 0 then + self:FinishEnemyTurn() + return +end +local m = self.Monsters[idx] +local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(idx) +self:SetEntityEnabled(base .. "/ActFrame", true) +_TimerService:SetTimerOnce(function() + m.block = 0 + local intent = m.intents[m.intentIdx] + if intent ~= nil then + if intent.kind == "Attack" then + local before = self.PlayerHp + self:DealDamageToPlayer(intent.value) + self:ShowPlayerDmgPop(before - self.PlayerHp) + elseif intent.kind == "Defend" then + m.block = m.block + intent.value end end + m.intentIdx = m.intentIdx + 1 + if m.intentIdx > #m.intents then + m.intentIdx = 1 + end + self:RenderCombat() + self:SetEntityEnabled(base .. "/ActFrame", false) + _TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15) +end, 0.45)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromIndex' }]), + method('FinishEnemyTurn', `self.TurnBusy = false +self:CheckCombatEnd() +if self.CombatOver == true then + return end -self:RenderCombat()`), +_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)`), method('CheckCombatEnd', `local anyAlive = false for i = 1, #self.Monsters do if self.Monsters[i].alive == true then anyAlive = true; break end -- 2.49.1 From e876a8ce3a7fae85a1388bdfbc57b0541d5e95a9 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 08:34:41 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat(combat-feel):=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=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=C2=B7=EC=9D=B4=ED=8E=99=ED=8A=B8=C2=B7=EA=B0=9C?= =?UTF-8?q?=EB=B3=84=20=EC=B0=A8=EB=A1=80=C2=B7=ED=8C=9D=EC=97=85)?= 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 | 272 ++- ui/DefaultGroup.ui | 1675 +++++++++++++++++- 2 files changed, 1937 insertions(+), 10 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index c1fa497..2af516a 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -343,6 +343,27 @@ "SyncDirection": 0, "Attributes": [], "Name": "ShopRelicBought" + }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "DragSlot" + }, + { + "Type": "boolean", + "DefaultValue": "false", + "SyncDirection": 0, + "Attributes": [], + "Name": "FxBusy" + }, + { + "Type": "boolean", + "DefaultValue": "false", + "SyncDirection": 0, + "Attributes": [], + "Name": "TurnBusy" } ], "Methods": [ @@ -664,7 +685,7 @@ "Name": null }, "Arguments": [], - "Code": "local endTurn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/EndTurnButton\")\nif endTurn ~= nil and endTurn.ButtonComponent ~= nil then\n\tif self.EndTurnHandler ~= nil then\n\t\tendTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)\n\t\tself.EndTurnHandler = nil\n\tend\n\tself.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)\nend\nlocal drawPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DrawPile\")\nif drawPile ~= nil and drawPile.ButtonComponent ~= nil then\n\tif self.DrawPileHandler ~= nil then\n\t\tdrawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)\n\t\tself.DrawPileHandler = nil\n\tend\n\tself.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"draw\") end)\nend\nlocal discardPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DiscardPile\")\nif discardPile ~= nil and discardPile.ButtonComponent ~= nil then\n\tif self.DiscardPileHandler ~= nil then\n\t\tdiscardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)\n\t\tself.DiscardPileHandler = nil\n\tend\n\tself.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"discard\") end)\nend\nlocal inspectClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckInspectHud/Close\")\nif inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then\n\tif self.DeckInspectCloseHandler ~= nil then\n\t\tinspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)\n\t\tself.DeckInspectCloseHandler = nil\n\tend\n\tself.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)\nend\nlocal allDeckButton = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton\")\nif allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then\n\tif self.AllDeckHandler ~= nil then\n\t\tallDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)\n\t\tself.AllDeckHandler = nil\n\tend\n\tself.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)\nend\nlocal allDeckClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckAllHud/Close\")\nif allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then\n\tif self.AllDeckCloseHandler ~= nil then\n\t\tallDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)\n\t\tself.AllDeckCloseHandler = nil\n\tend\n\tself.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)\nend\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.ButtonComponent ~= nil then\n\t\tcardEntity:ConnectEvent(ButtonClickEvent, function() self:PlayCard(i) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal rc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i))\n\tif rc ~= nil and rc.ButtonComponent ~= nil then\n\t\trc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)\n\tend\nend\nlocal skip = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Skip\")\nif skip ~= nil and skip.ButtonComponent ~= nil then\n\tskip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)\nend\nlocal mapNodeIds = { \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"BOSS\" }\nfor i = 1, #mapNodeIds do\n\tlocal nid = mapNodeIds[i]\n\tlocal mn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud/Node_\" .. nid)\n\tif mn ~= nil and mn.ButtonComponent ~= nil then\n\t\tmn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal sc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i))\n\tif sc ~= nil and sc.ButtonComponent ~= nil then\n\t\tsc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)\n\tend\nend\nlocal shopLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Leave\")\nif shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then\n\tshopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nlocal shopRelic = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Relic\")\nif shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then\n\tshopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)\nend\nlocal restLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RestHud/Leave\")\nif restLeave ~= nil and restLeave.ButtonComponent ~= nil then\n\trestLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nfor i = 1, 4 do\n\tlocal ms = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(i))\n\tif ms ~= nil and ms.ButtonComponent ~= nil then\n\t\tms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)\n\tend\nend", + "Code": "local endTurn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/EndTurnButton\")\nif endTurn ~= nil and endTurn.ButtonComponent ~= nil then\n\tif self.EndTurnHandler ~= nil then\n\t\tendTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)\n\t\tself.EndTurnHandler = nil\n\tend\n\tself.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)\nend\nlocal drawPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DrawPile\")\nif drawPile ~= nil and drawPile.ButtonComponent ~= nil then\n\tif self.DrawPileHandler ~= nil then\n\t\tdrawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)\n\t\tself.DrawPileHandler = nil\n\tend\n\tself.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"draw\") end)\nend\nlocal discardPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DiscardPile\")\nif discardPile ~= nil and discardPile.ButtonComponent ~= nil then\n\tif self.DiscardPileHandler ~= nil then\n\t\tdiscardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)\n\t\tself.DiscardPileHandler = nil\n\tend\n\tself.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"discard\") end)\nend\nlocal inspectClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckInspectHud/Close\")\nif inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then\n\tif self.DeckInspectCloseHandler ~= nil then\n\t\tinspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)\n\t\tself.DeckInspectCloseHandler = nil\n\tend\n\tself.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)\nend\nlocal allDeckButton = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton\")\nif allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then\n\tif self.AllDeckHandler ~= nil then\n\t\tallDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)\n\t\tself.AllDeckHandler = nil\n\tend\n\tself.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)\nend\nlocal allDeckClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckAllHud/Close\")\nif allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then\n\tif self.AllDeckCloseHandler ~= nil then\n\t\tallDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)\n\t\tself.AllDeckCloseHandler = nil\n\tend\n\tself.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)\nend\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then\n\t\tcardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end)\n\t\tcardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end)\n\t\tcardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal rc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i))\n\tif rc ~= nil and rc.ButtonComponent ~= nil then\n\t\trc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)\n\tend\nend\nlocal skip = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Skip\")\nif skip ~= nil and skip.ButtonComponent ~= nil then\n\tskip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)\nend\nlocal mapNodeIds = { \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"BOSS\" }\nfor i = 1, #mapNodeIds do\n\tlocal nid = mapNodeIds[i]\n\tlocal mn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud/Node_\" .. nid)\n\tif mn ~= nil and mn.ButtonComponent ~= nil then\n\t\tmn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal sc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i))\n\tif sc ~= nil and sc.ButtonComponent ~= nil then\n\t\tsc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)\n\tend\nend\nlocal shopLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Leave\")\nif shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then\n\tshopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nlocal shopRelic = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Relic\")\nif shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then\n\tshopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)\nend\nlocal restLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RestHud/Leave\")\nif restLeave ~= nil and restLeave.ButtonComponent ~= nil then\n\trestLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nfor i = 1, 4 do\n\tlocal ms = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(i))\n\tif ms ~= nil and ms.ButtonComponent ~= nil then\n\t\tms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)\n\tend\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -694,7 +715,7 @@ "Name": null }, "Arguments": [], - "Code": "if self.CombatOver == 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()\nself:CheckCombatEnd()\nif self.CombatOver == true then\n\treturn\nend\n_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)", + "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()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1100,12 +1121,125 @@ "Name": "slot" } ], - "Code": "if self.CombatOver == 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:DealDamageToTarget(c.damage)\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, c.damage)\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()", "Scope": 2, "ExecSpace": 6, "Attributes": [], "Name": "PlayCard" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "slot" + } + ], + "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil or self.Hand[slot] == nil then\n\treturn\nend\nself.DragSlot = slot", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnCardDragBegin" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "slot" + }, + { + "Type": "any", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "touchPoint" + } + ], + "Code": "if self.DragSlot ~= slot then\n\treturn\nend\nlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\tlocal ui = _UILogic:ScreenToUIPosition(touchPoint)\n\te.UITransformComponent.anchoredPosition = Vector2(ui.x, ui.y + 360)\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnCardDrag" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "slot" + }, + { + "Type": "any", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "touchPoint" + } + ], + "Code": "if self.DragSlot ~= slot then\n\treturn\nend\nself.DragSlot = 0\nlocal cardXs = { -400, -200, 0, 200, 400 }\nlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0)\nend\nself:ResolveCardDrop(slot, touchPoint)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnCardDragEnd" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "slot" + }, + { + "Type": "any", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "touchPoint" + } + ], + "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true 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 c.kind == \"Attack\" then\n\tlocal best = 0\n\tlocal bestDist = 200\n\tfor i = 1, #self.Monsters do\n\t\tlocal m = self.Monsters[i]\n\t\tif m.alive == true and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then\n\t\t\tlocal wp = m.entity.TransformComponent.WorldPosition\n\t\t\tlocal sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))\n\t\t\tlocal dx = sp.x - touchPoint.x\n\t\t\tlocal dy = sp.y - touchPoint.y\n\t\t\tlocal d = math.sqrt(dx * dx + dy * dy)\n\t\t\tif d < bestDist then\n\t\t\t\tbestDist = d\n\t\t\t\tbest = i\n\t\t\tend\n\t\tend\n\tend\n\tif best > 0 then\n\t\tself.TargetIndex = best\n\t\tself:PlayCard(slot)\n\tend\nelse\n\tlocal ui = _UILogic:ScreenToUIPosition(touchPoint)\n\tif ui.y > -180 then\n\t\tself:PlayCard(slot)\n\tend\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "ResolveCardDrop" + }, { "Return": { "Type": "void", @@ -1152,6 +1286,43 @@ "Attributes": [], "Name": "DealDamageToTarget" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "targetIndex" + }, + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "image" + }, + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "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)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "PlayAttackFx" + }, { "Return": { "Type": "void", @@ -1169,7 +1340,7 @@ "Name": "slot" } ], - "Code": "local m = self.Monsters[slot]\nif m == nil then\n\treturn\nend\nm.alive = false\nif m.entity ~= nil and isvalid(m.entity) then\n\tm.entity:SetVisible(false)\nend\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(slot), false)\nfor i = 1, #self.Monsters do\n\tif self.Monsters[i].alive == true then self.TargetIndex = i; break end\nend", + "Code": "local m = self.Monsters[slot]\nif m == nil then\n\treturn\nend\nm.alive = false\nif m.entity ~= nil and isvalid(m.entity) then\n\tlocal ent = m.entity\n\t_TimerService:SetTimerOnce(function() if isvalid(ent) then ent:SetVisible(false) end end, 0.4)\nend\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(slot), false)\nfor i = 1, #self.Monsters do\n\tif self.Monsters[i].alive == true then self.TargetIndex = i; break end\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1207,12 +1378,50 @@ "Name": null }, "Arguments": [], - "Code": "for i = 1, #self.Monsters do\n\tlocal m = self.Monsters[i]\n\tif m.alive == true then\n\t\tm.block = 0\n\t\tlocal intent = m.intents[m.intentIdx]\n\t\tif intent ~= nil then\n\t\t\tif intent.kind == \"Attack\" then\n\t\t\t\tself:DealDamageToPlayer(intent.value)\n\t\t\telseif intent.kind == \"Defend\" then\n\t\t\t\tm.block = m.block + intent.value\n\t\t\tend\n\t\tend\n\t\tm.intentIdx = m.intentIdx + 1\n\t\tif m.intentIdx > #m.intents then\n\t\t\tm.intentIdx = 1\n\t\tend\n\tend\nend\nself:RenderCombat()", + "Code": "self.TurnBusy = true\nself:EnemyActStep(1)", "Scope": 2, "ExecSpace": 6, "Attributes": [], "Name": "EnemyTurn" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "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)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "EnemyActStep" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "self.TurnBusy = false\nself:CheckCombatEnd()\nif self.CombatOver == true then\n\treturn\nend\n_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "FinishEnemyTurn" + }, { "Return": { "Type": "void", @@ -1266,6 +1475,59 @@ "Attributes": [], "Name": "RenderCombat" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "slot" + }, + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "amount" + } + ], + "Code": "local base = \"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(slot) .. \"/DmgPop\"\nself:SetText(base, \"-\" .. string.format(\"%d\", amount))\nself:SetEntityEnabled(base, true)\n_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "ShowDmgPop" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "amount" + } + ], + "Code": "local base = \"/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop\"\nif amount > 0 then\n\tself:SetText(base, \"-\" .. string.format(\"%d\", amount))\nelse\n\tself:SetText(base, \"막음\")\nend\nself:SetEntityEnabled(base, true)\n_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "ShowPlayerDmgPop" + }, { "Return": { "Type": "void", diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index 64118e1..c8e1478 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -1209,7 +1209,7 @@ { "id": "cad00001-0000-4000-8000-000000000001", "path": "/ui/DefaultGroup/CardHand/Card1", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card1", "path": "/ui/DefaultGroup/CardHand/Card1", @@ -1389,6 +1389,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -1961,7 +1965,7 @@ { "id": "cad00005-0000-4000-8000-000000000005", "path": "/ui/DefaultGroup/CardHand/Card2", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card2", "path": "/ui/DefaultGroup/CardHand/Card2", @@ -2141,6 +2145,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -2713,7 +2721,7 @@ { "id": "cad00009-0000-4000-8000-000000000009", "path": "/ui/DefaultGroup/CardHand/Card3", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card3", "path": "/ui/DefaultGroup/CardHand/Card3", @@ -2893,6 +2901,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -3465,7 +3477,7 @@ { "id": "cad0000d-0000-4000-8000-00000000000d", "path": "/ui/DefaultGroup/CardHand/Card4", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card4", "path": "/ui/DefaultGroup/CardHand/Card4", @@ -3645,6 +3657,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -4217,7 +4233,7 @@ { "id": "cad00011-0000-4000-8000-000000000011", "path": "/ui/DefaultGroup/CardHand/Card5", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card5", "path": "/ui/DefaultGroup/CardHand/Card5", @@ -4397,6 +4413,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -9572,6 +9592,147 @@ "@version": 1 } }, + { + "id": "0cb000f1-0000-4000-8000-00000cb000f1", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/ActFrame", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "ActFrame", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/ActFrame", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 0, + "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": 78, + "y": 54 + }, + "OffsetMin": { + "x": -78, + "y": -54 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 156, + "y": 108 + }, + "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.95, + "g": 0.3, + "b": 0.25, + "a": 0.3 + }, + "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": "0cb0003d-0000-4000-8000-00000cb0003d", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/Name", @@ -10418,6 +10579,194 @@ "@version": 1 } }, + { + "id": "0cb000fb-0000-4000-8000-00000cb000fb", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/DmgPop", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "DmgPop", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot1/DmgPop", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 9, + "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": 60, + "y": 75 + }, + "OffsetMin": { + "x": -60, + "y": 45 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 120, + "y": 30 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 60 + }, + "Position": { + "x": 0, + "y": 60, + "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": 0.35, + "b": 0.3, + "a": 1 + }, + "FontSize": 24, + "MaxSize": 24, + "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", @@ -10747,6 +11096,147 @@ "@version": 1 } }, + { + "id": "0cb000f2-0000-4000-8000-00000cb000f2", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/ActFrame", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "ActFrame", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/ActFrame", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 0, + "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": 78, + "y": 54 + }, + "OffsetMin": { + "x": -78, + "y": -54 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 156, + "y": 108 + }, + "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.95, + "g": 0.3, + "b": 0.25, + "a": 0.3 + }, + "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": "0cb0003e-0000-4000-8000-00000cb0003e", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/Name", @@ -11593,6 +12083,194 @@ "@version": 1 } }, + { + "id": "0cb000fc-0000-4000-8000-00000cb000fc", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/DmgPop", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "DmgPop", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot2/DmgPop", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 9, + "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": 60, + "y": 75 + }, + "OffsetMin": { + "x": -60, + "y": 45 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 120, + "y": 30 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 60 + }, + "Position": { + "x": 0, + "y": 60, + "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": 0.35, + "b": 0.3, + "a": 1 + }, + "FontSize": 24, + "MaxSize": 24, + "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", @@ -11922,6 +12600,147 @@ "@version": 1 } }, + { + "id": "0cb000f3-0000-4000-8000-00000cb000f3", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/ActFrame", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "ActFrame", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/ActFrame", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 0, + "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": 78, + "y": 54 + }, + "OffsetMin": { + "x": -78, + "y": -54 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 156, + "y": 108 + }, + "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.95, + "g": 0.3, + "b": 0.25, + "a": 0.3 + }, + "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": "0cb0003f-0000-4000-8000-00000cb0003f", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/Name", @@ -12768,6 +13587,194 @@ "@version": 1 } }, + { + "id": "0cb000fd-0000-4000-8000-00000cb000fd", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/DmgPop", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "DmgPop", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot3/DmgPop", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 9, + "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": 60, + "y": 75 + }, + "OffsetMin": { + "x": -60, + "y": 45 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 120, + "y": 30 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 60 + }, + "Position": { + "x": 0, + "y": 60, + "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": 0.35, + "b": 0.3, + "a": 1 + }, + "FontSize": 24, + "MaxSize": 24, + "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", @@ -13097,6 +14104,147 @@ "@version": 1 } }, + { + "id": "0cb000f4-0000-4000-8000-00000cb000f4", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/ActFrame", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "ActFrame", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/ActFrame", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 0, + "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": 78, + "y": 54 + }, + "OffsetMin": { + "x": -78, + "y": -54 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 156, + "y": 108 + }, + "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.95, + "g": 0.3, + "b": 0.25, + "a": 0.3 + }, + "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": "0cb00040-0000-4000-8000-00000cb00040", "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/Name", @@ -13943,6 +15091,194 @@ "@version": 1 } }, + { + "id": "0cb000fe-0000-4000-8000-00000cb000fe", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/DmgPop", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "DmgPop", + "path": "/ui/DefaultGroup/CombatHud/MonsterSlot4/DmgPop", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 9, + "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": 60, + "y": 75 + }, + "OffsetMin": { + "x": -60, + "y": 45 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 120, + "y": 30 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 60 + }, + "Position": { + "x": 0, + "y": 60, + "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": 0.35, + "b": 0.3, + "a": 1 + }, + "FontSize": 24, + "MaxSize": 24, + "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", @@ -15071,6 +16407,194 @@ "@version": 1 } }, + { + "id": "0cb00104-0000-4000-8000-00000cb00104", + "path": "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "DmgPop", + "path": "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 5, + "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": 96, + "y": 55 + }, + "OffsetMin": { + "x": -64, + "y": 25 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 160, + "y": 30 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 16, + "y": 40 + }, + "Position": { + "x": 16, + "y": 40, + "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": 0.4, + "b": 0.35, + "a": 1 + }, + "FontSize": 22, + "MaxSize": 22, + "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": "0cb000c8-0000-4000-8000-00000cb000c8", "path": "/ui/DefaultGroup/CombatHud/TopBar", @@ -16011,6 +17535,147 @@ "@version": 1 } }, + { + "id": "0cb000e6-0000-4000-8000-00000cb000e6", + "path": "/ui/DefaultGroup/CombatHud/SkillFx", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", + "jsonString": { + "name": "SkillFx", + "path": "/ui/DefaultGroup/CombatHud/SkillFx", + "nameEditable": true, + "enable": false, + "visible": true, + "localize": true, + "displayOrder": 30, + "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": 55, + "y": 55 + }, + "OffsetMin": { + "x": -55, + "y": -55 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 110, + "y": 110 + }, + "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": 1, + "g": 1, + "b": 1, + "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": 0, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0cb00002-0000-4000-8000-00000cb00002", "path": "/ui/DefaultGroup/CombatHud/Result", -- 2.49.1