From 5e2fd5db2298e91cb694ef87dd6cc316199d88ce Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 30 Jun 2026 00:49:30 +0900 Subject: [PATCH] Revert "Merge branch 'main' of https://gitea.gahusb.synology.me/gahusb/maplecontest" This reverts commit 17200d47ec066bef5314ff81620ed474c9ad5dd6, reversing changes made to 95d6155086a20fca9e931a4f0ee98f5464d9040f. --- data/cards.json | 18 ++++---- tools/balance/sim-balance.mjs | 17 ++++--- tools/balance/sim-balance.test.mjs | 51 --------------------- tools/deck/cb/combat.mjs | 20 ++------ tools/deck/cb/deckturn.mjs | 7 +-- tools/deck/cb/hand.mjs | 73 ++++++++++++++---------------- tools/deck/gen-slaydeck.mjs | 1 - 7 files changed, 58 insertions(+), 129 deletions(-) diff --git a/data/cards.json b/data/cards.json index b5f294d..0548c53 100644 --- a/data/cards.json +++ b/data/cards.json @@ -89,10 +89,10 @@ "Rage": { "name": "분노", "cost": 1, - "kind": "Attack", + "kind": "Power", "aoe": true, "damage": 4, - "desc": "모든 적에게 피해를 4 줍니다.", + "desc": "매 턴 시작 시 힘 +1", "image": "379d86e3de064959aa4612f71e84ccfb", "class": "warrior", "rarity": "legend" @@ -537,7 +537,7 @@ "kind": "Skill", "class": "rogue", "rarity": "normal", - "desc": "카드를 1장 버리고, 이번 턴에 준 피해만큼 방어를 얻습니다.", + "desc": "카드를 1장 뽑습니다. 카드를 1장 버립니다.", "blockPerDamageDealtThisTurn": 1, "discard": 1, "image": "c1e19219745e44c39ae6ac2f77e347d9" @@ -712,7 +712,7 @@ "kind": "Attack", "class": "rogue", "rarity": "unique", - "desc": "피해를 8 줍니다.", + "desc": "피해를 8 줍니다. 이번 턴에 카드를 사용할 때마다, 대상 적이 체력을 2 잃습니다.", "damage": 8, "image": "92a5020c978c46bdabab910598118b86" }, @@ -845,7 +845,7 @@ "kind": "Skill", "class": "rogue", "rarity": "unique", - "desc": "카드를 1장 뽑습니다.", + "desc": "모든 적에게 부여된 중독과 동일한 만큼의 방어도를 얻습니다. 소멸.", "draw": 1, "image": "0946f69d84464df29b24b94c744c868d" }, @@ -943,7 +943,7 @@ "kind": "Skill", "class": "rogue", "rarity": "unique", - "desc": "교활. 에너지를 1 얻습니다.", + "desc": "교활. 을 얻습니다.", "gainEnergy": 1, "sly": true, "image": "c1e19219745e44c39ae6ac2f77e347d9" @@ -1098,7 +1098,7 @@ "kind": "Skill", "class": "rogue", "rarity": "legend", - "desc": "에너지를 모두 사용하고, 사용한 에너지만큼 적에게 약화를 부여합니다.", + "desc": "적이 힘을 X 잃습니다. 약화를 X 부여합니다. 소멸.", "useAllEnergy": true, "xWeakPerEnergy": 1, "image": "0946f69d84464df29b24b94c744c868d" @@ -1109,7 +1109,7 @@ "kind": "Skill", "class": "rogue", "rarity": "legend", - "desc": "에너지를 1 얻습니다. 카드를 2장 뽑습니다. 소멸.", + "desc": "를 얻습니다. 카드를 2장 뽑습니다. 소멸.", "draw": 2, "gainEnergy": 1, "image": "91a2d1c16cb041549adbf1a0d7b1f37f" @@ -1183,7 +1183,7 @@ "kind": "Skill", "class": "rogue", "rarity": "legend", - "desc": "카드를 1장 뽑습니다.", + "desc": "대상 적에게 소멸된 카드 더미에 있는 모든 표창을 사용합니다.", "draw": 1, "image": "1b0f2dc8abd0434990eee1befefcbe0d" }, diff --git a/tools/balance/sim-balance.mjs b/tools/balance/sim-balance.mjs index 20bcb5e..9f3988f 100644 --- a/tools/balance/sim-balance.mjs +++ b/tools/balance/sim-balance.mjs @@ -55,8 +55,7 @@ export function calcAttack(base, str, weak, vulnOnTarget) { } export function calcEnemyAttack(base, str, weak, vulnOnTarget, strengthLoss = 0) { - // Lua EnemyActStep 동기화: 힘 손실은 (value+str) 전체에서 차감(음수 힘 허용), 최종 calcAttack이 0 클램프. - return calcAttack(base, str - strengthLoss, weak, vulnOnTarget); + return calcAttack(base, Math.max(0, str - strengthLoss), weak, vulnOnTarget); } // 방어 우선 차감 후 hp 적용 → { hp, block } @@ -343,7 +342,7 @@ export function simulateCombat(data, rng, stats) { if (c.damagePerDiscardedThisTurn) base += turnDiscardedCards * c.damagePerDiscardedThisTurn; if (c.damagePerSkillInHand) base += countOtherHandSkills(id) * c.damagePerSkillInHand; if (c.damagePerCardDrawnThisCombat) base += cardsDrawnThisCombat * c.damagePerCardDrawnThisCombat; - if (c.kind === 'Attack' && turnCardsPlayedThisTurn === 0 && c.firstCardDamageBonus) base += c.firstCardDamageBonus; + if (c.class === 'Attack' && turnCardsPlayedThisTurn === 0 && c.firstCardDamageBonus) base += c.firstCardDamageBonus; if (c.class === 'shiv') { if (powerFieldTotal('shivDamageBonus') > 0) base += powerFieldTotal('shivDamageBonus'); if (!shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) base += powerFieldTotal('firstShivDamageBonus'); @@ -423,9 +422,6 @@ export function simulateCombat(data, rng, stats) { const hitN = (c.hits || 1) + bonusHits; let useAoe = c.aoe === true; if (c.class === 'shiv' && shivAoeThisCombat === true) useAoe = true; - if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) { - shivFirstDamageBonusUsed = true; - } const perHit = calcAttack(baseDamage || 0, pStr, pWeak, 0) * turnAttackMultiplier; const dealToTarget = (target, amount) => { if (!target || !target.alive) return { killed: false, dealt: 0 }; @@ -519,6 +515,9 @@ export function simulateCombat(data, rng, stats) { } } } + if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) { + shivFirstDamageBonusUsed = true; + } } } if (c.strength) pStr += c.strength; @@ -565,7 +564,7 @@ export function simulateCombat(data, rng, stats) { } } if (c.blockPerDamageDealtThisTurn && c.blockPerDamageDealtThisTurn > 0 && c.kind !== 'Power') { - blockGained += addBlock(Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn)); + blockGained += Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn); } if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained); } @@ -659,7 +658,7 @@ export function simulateCombat(data, rng, stats) { const baseCost = c.cost || 0; const combatReduction = combatCardCostReduction[id] || 0; const cost = handCostZeroThisTurn === true ? 0 : (c.useAllEnergy === true ? energy : (skillFree ? 0 : (c.kind === 'Skill' ? Math.max(0, baseCost - skillCostReductionThisTurn) : baseCost))); - const finalCost = c.useAllEnergy === true ? cost : Math.max(0, cost - combatReduction); + const finalCost = Math.max(0, cost - combatReduction); energy -= finalCost; resolveCardEffects(id, c, finalCost); const playedBlock = powerFieldTotal('cardPlayedBlock'); @@ -722,7 +721,7 @@ export function simulateCombat(data, rng, stats) { const it = m.intents.length ? m.intents[Math.floor(rng() * m.intents.length)] : null; if (it) { if (it.kind === 'Attack') { - const atk = calcEnemyAttack(it.value, m.str, m.weak, pVuln, enemyStrengthLossThisTurn); + const atk = calcAttack(it.value, Math.max(0, m.str - enemyStrengthLossThisTurn), m.weak, pVuln); const beforeHp = pHp; let incoming = atk; if (pIntangible > 0 && incoming > 1) incoming = 1; diff --git a/tools/balance/sim-balance.test.mjs b/tools/balance/sim-balance.test.mjs index 3bf6a8c..071ee0b 100644 --- a/tools/balance/sim-balance.test.mjs +++ b/tools/balance/sim-balance.test.mjs @@ -262,19 +262,6 @@ test('simulateCombat: 카드 취약 부여가 같은 카드 피해에 선적용 assert.equal(r.turns, 1); }); -test('simulateCombat: firstCardDamageBonus가 턴 첫 카드에 적용 (kind===Attack, Lua 동기화)', () => { - // ChargedBlow처럼 class=warrior·kind=Attack인 카드의 첫-카드 보너스. - // 게이트가 class==="Attack"이면 영구 false라 미발동(버그) → 5뎀/2턴. - // kind==="Attack"이면 5+2=7 → 1턴 처치. - const data = { - cards: { CB: { name: '차지블로우', cost: 3, kind: 'Attack', class: 'warrior', damage: 5, firstCardDamageBonus: 2 } }, - starterDeck: ['CB', 'CB', 'CB', 'CB', 'CB'], - monsters: [{ name: '적', maxHp: 7, intents: [{ kind: 'Defend', value: 0 }] }], - }; - const r = simulateCombat(data, mulberry32(1)); - assert.equal(r.turns, 1); -}); - test('simulateCombat: Power(매턴 힘) 누적', () => { const data = { cards: { @@ -895,44 +882,6 @@ test("calcEnemyAttack: enemyStrengthLossThisTurn reduces enemy attack damage", ( assert.equal(calcEnemyAttack(10, 6, 0, 0, 0), 16); }); -test("calcEnemyAttack: 힘 손실이 base 아래로 공격을 낮춘다 (음수 힘, Lua 동기화)", () => { - // 적 str=0, loss=6 → 힘 -6 → 10-6=4. JS가 str을 0에서 클램프하면 10(버그). Lua는 전체에서 차감. - assert.equal(calcEnemyAttack(10, 0, 0, 0, 6), 4); - assert.equal(calcEnemyAttack(10, 3, 0, 0, 6), 7); - assert.equal(calcEnemyAttack(5, 0, 0, 0, 6), 0); // 5-6=-1 → 0 클램프 -}); - -test('simulateCombat: firstShivDamageBonus는 턴당 첫 Shiv에만 적용 (Lua 동기화)', () => { - // PhantomBlades(firstShivDamageBonus 9) 활성. 턴당 3 Shiv 사용(에너지3·cost1). - // 정답(첫 Shiv만 +9): 턴1 = 10+1+1=12 → 13HP에 1 남김 → 2턴. - // 버그(모든 Shiv +9): 턴1 = 10*3=30 → 1턴. - const data = { - cards: { - PhantomBlades: { name: '환영검', cost: 0, kind: 'Power', firstShivDamageBonus: 9 }, - Shiv: { name: '시브', cost: 1, kind: 'Attack', class: 'shiv', damage: 1 }, - }, - starterDeck: ['PhantomBlades', 'Shiv', 'Shiv', 'Shiv', 'Shiv'], - monsters: [{ name: '적', maxHp: 13, intents: [{ kind: 'Attack', value: 0 }] }], - }; - const r = simulateCombat(data, mulberry32(1)); - assert.equal(r.turns, 2); -}); - -test('simulateCombat: blockPerDamageDealtThisTurn이 실제 방어를 부여 (Lua 동기화)', () => { - // 매턴 Hit(5뎀) → Guard(준 피해만큼 방어 5) → 적 공격 5 상쇄. - // 수정(실제 방어): 무한 생존 → 무승부. 버그(방어 미부여): 매턴 5피해 → 사망. - const data = { - cards: { - Hit: { name: '타격', cost: 2, kind: 'Attack', damage: 5 }, - Guard: { name: '대비', cost: 1, kind: 'Skill', blockPerDamageDealtThisTurn: 1 }, - }, - starterDeck: ['Hit', 'Guard'], - monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Attack', value: 5 }] }], - }; - const r = simulateCombat(data, mulberry32(1)); - assert.equal(r.draw, true); -}); - test("simulateCombat: repeatOnKill repeats an attack until no kill occurs", () => { const shared = { cards: { diff --git a/tools/deck/cb/combat.mjs b/tools/deck/cb/combat.mjs index 0fa2ee1..07463ac 100644 --- a/tools/deck/cb/combat.mjs +++ b/tools/deck/cb/combat.mjs @@ -52,14 +52,14 @@ if self.HandCostZeroThisTurn == true then elseif c.useAllEnergy == true then cost = self.Energy end -if c.kind == "Skill" and c.useAllEnergy ~= true and self.NextSkillCostZero == true then +if c.kind == "Skill" and self.NextSkillCostZero == true then cost = 0 skillFree = true end -if c.kind == "Skill" and c.useAllEnergy ~= true and self.SkillCostReductionThisTurn ~= nil and self.SkillCostReductionThisTurn > 0 then +if c.kind == "Skill" and self.SkillCostReductionThisTurn ~= nil and self.SkillCostReductionThisTurn > 0 then cost = math.max(0, cost - self.SkillCostReductionThisTurn) end -if c.useAllEnergy ~= true and self.CombatCardCostReduction ~= nil and self.CombatCardCostReduction[cardId] ~= nil then +if self.CombatCardCostReduction ~= nil and self.CombatCardCostReduction[cardId] ~= nil then cost = math.max(0, cost - self.CombatCardCostReduction[cardId]) end if c.kind == "Skill" and self.NextSkillRepeatCount ~= nil and self.NextSkillRepeatCount > 0 then @@ -381,7 +381,7 @@ for i = 1, #self.Monsters do local m = self.Monsters[i] if m ~= nil and m.alive == true then local dmg = amount - if isAttack == true and m.vuln > 0 then + if m.vuln > 0 then dmg = math.floor(dmg * 1.5) end if m.block > 0 then @@ -392,12 +392,6 @@ for i = 1, #self.Monsters do m.hp = m.hp - dmg if dmg > 0 then self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + dmg - if isAttack == true then - local poison = self:AddPowerFieldTotal("attackPoison") - if poison ~= nil and poison > 0 then - self:ApplyPoisonToMonster(m, poison) - end - end end self:ShowDmgPop(i, dmg) self:MonsterHitMotion(i) @@ -415,7 +409,6 @@ self:RenderCombat() self:CheckCombatEnd() return killCount > 0`, [ { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, - { Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'isAttack' }, ], 0, 'boolean'), method('PlayAttackFx', `local m = self.Monsters[targetIndex] if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then @@ -689,10 +682,7 @@ self.NextTurnAddCards = {} self:UpdateDiscardPrompt() self:RenderHand(false) self:RenderPiles()`), - method('CheckCombatEnd', `if self.CombatOver == true then - return -end -local anyAlive = false + method('CheckCombatEnd', `local anyAlive = false for i = 1, #self.Monsters do if self.Monsters[i].alive == true then anyAlive = true; break end end diff --git a/tools/deck/cb/deckturn.mjs b/tools/deck/cb/deckturn.mjs index 828688c..d1f4e6d 100644 --- a/tools/deck/cb/deckturn.mjs +++ b/tools/deck/cb/deckturn.mjs @@ -10,11 +10,7 @@ for i = #list, 2, -1 do \tlocal j = math.random(1, i) \tlist[i], list[j] = list[j], list[i] end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]), - method('BindButtons', `if self.ButtonsBound == true then - return -end -self.ButtonsBound = true -local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton") + method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton") if endTurn ~= nil and (endTurn.ButtonComponent ~= nil or endTurn:AddComponent("ButtonComponent") ~= nil) then if self.EndTurnHandler ~= nil then endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler) @@ -475,7 +471,6 @@ for i = 1, amount do \tlocal cardId = table.remove(self.DrawPile) \ttable.insert(drawnCards, cardId) \tself.CardsDrawnThisCombat = (self.CardsDrawnThisCombat or 0) + 1 -\tself:ApplyDrawTrigger() \tif #self.Hand >= 10 then \t\ttable.insert(self.DiscardPile, cardId) \t\tself:TriggerSly(cardId) diff --git a/tools/deck/cb/hand.mjs b/tools/deck/cb/hand.mjs index e5b8c0c..62b98d9 100644 --- a/tools/deck/cb/hand.mjs +++ b/tools/deck/cb/hand.mjs @@ -3,42 +3,6 @@ import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; export const handMethods = [ - method('ApplyDrawTrigger', `if self.Monsters == nil then - return -end -local drawDamage = self:AddPowerFieldTotal("drawDamage") + (self.DrawDamageThisTurn or 0) -local drawPoison = self:AddPowerFieldTotal("drawPoison") + (self.DrawPoisonThisTurn or 0) -if (drawDamage ~= nil and drawDamage > 0) or (drawPoison ~= nil and drawPoison > 0) then - for mi = 1, #self.Monsters do - local m2 = self.Monsters[mi] - if m2 ~= nil and m2.alive == true then - local dmg = drawDamage or 0 - if m2.vuln > 0 then - dmg = math.floor(dmg * 1.5) - end - if m2.block > 0 then - local absorbed = math.min(m2.block, dmg) - m2.block = m2.block - absorbed - dmg = dmg - absorbed - end - if drawPoison ~= nil and drawPoison > 0 then - self:ApplyPoisonToMonster(m2, drawPoison) - end - if dmg > 0 then - m2.hp = m2.hp - dmg - self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + dmg - end - self:ShowDmgPop(mi, dmg) - self:MonsterHitMotion(mi) - if m2.hp <= 0 then - m2.hp = 0 - self:KillMonster(m2.slot) - end - end - end - self:RenderCombat() - self:CheckCombatEnd() -end`), method('GetHandSlotX', `local n = 0 if self.Hand ~= nil then n = #self.Hand @@ -347,7 +311,7 @@ end if c.damagePerCardDrawnThisCombat ~= nil then base2 = base2 + (self.CardsDrawnThisCombat or 0) * c.damagePerCardDrawnThisCombat end -if c.kind == "Attack" and (self.TurnCardsPlayedThisTurn or 0) == 0 and c.firstCardDamageBonus ~= nil then +if c.class == "Attack" and (self.TurnCardsPlayedThisTurn or 0) == 0 and c.firstCardDamageBonus ~= nil then base2 = base2 + c.firstCardDamageBonus end if c.class == "shiv" then @@ -542,7 +506,7 @@ if c.kind == "Attack" then local function resolveAttackRound() local roundKilled = false if useAoe == true then - local killed = self:DealDamageToAllMonsters(total, true) + local killed = self:DealDamageToAllMonsters(total) if killed == true then roundKilled = true end elseif c.randomTargetEachHit == true then for h = 1, hitN do @@ -717,6 +681,39 @@ if c.drawSkillBlock ~= nil and c.drawSkillBlock > 0 then end end end +local drawDamage = self:AddPowerFieldTotal("drawDamage") + (self.DrawDamageThisTurn or 0) +local drawPoison = self:AddPowerFieldTotal("drawPoison") + (self.DrawPoisonThisTurn or 0) +if (drawDamage ~= nil and drawDamage > 0) or (drawPoison ~= nil and drawPoison > 0) then + for mi = 1, #self.Monsters do + local m2 = self.Monsters[mi] + if m2 ~= nil and m2.alive == true then + local dmg = drawDamage or 0 + if m2.vuln > 0 then + dmg = math.floor(dmg * 1.5) + end + if m2.block > 0 then + local absorbed = math.min(m2.block, dmg) + m2.block = m2.block - absorbed + dmg = dmg - absorbed + end + if drawPoison ~= nil and drawPoison > 0 then + self:ApplyPoisonToMonster(m2, drawPoison) + end + if dmg > 0 then + m2.hp = m2.hp - dmg + self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + dmg + end + self:ShowDmgPop(mi, dmg) + self:MonsterHitMotion(mi) + if m2.hp <= 0 then + m2.hp = 0 + self:KillMonster(m2.slot) + end + end + end + self:RenderCombat() + self:CheckCombatEnd() +end if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then self:AddCardsToHand("Shiv", c.addShiv) end`, [ diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index eb063c4..7a899c9 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -61,7 +61,6 @@ function writeCodeblocks() { prop('any', 'AllDeckCloseHandler'), prop('number', 'SoulPoints', '0'), prop('boolean', 'LobbyBound', 'false'), - prop('boolean', 'ButtonsBound', 'false'), prop('number', 'LobbyTpTries', '0'), prop('boolean', 'CodexMode', 'false'), prop('any', 'CodexCards'),