diff --git a/tools/balance/sim-balance.mjs b/tools/balance/sim-balance.mjs index f9ec7dd..8ac149e 100644 --- a/tools/balance/sim-balance.mjs +++ b/tools/balance/sim-balance.mjs @@ -29,6 +29,13 @@ export function shuffle(arr, rng) { // 공격 피해 공식 — Lua CalcPlayerAttack(힘·약화) + DealDamageToTarget(취약)과 동기화. // floor((base + str) * (weak>0 ? 0.75 : 1)) → floor(... * (vulnOnTarget>0 ? 1.5 : 1)) +// 보상 카드 등급 추첨 (Lua OfferReward 미러) — roll ∈ 1..100, normal 70 / unique 25 / legend 5 +export function rarityForRoll(roll) { + if (roll > 95) return 'legend'; + if (roll > 70) return 'unique'; + return 'normal'; +} + export function calcAttack(base, str, weak, vulnOnTarget) { let dmg = base + str; if (weak > 0) dmg = Math.floor(dmg * 0.75); diff --git a/tools/balance/sim-balance.test.mjs b/tools/balance/sim-balance.test.mjs index ce837f0..d9130a1 100644 --- a/tools/balance/sim-balance.test.mjs +++ b/tools/balance/sim-balance.test.mjs @@ -1,9 +1,18 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { - mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, calcAttack, + mulberry32, applyDamage, chooseAction, chooseTarget, simulateCombat, runBatch, calcAttack, rarityForRoll, } from './sim-balance.mjs'; +test('rarityForRoll: 70/25/5 경계 (Lua OfferReward 미러)', () => { + assert.equal(rarityForRoll(1), 'normal'); + assert.equal(rarityForRoll(70), 'normal'); + assert.equal(rarityForRoll(71), 'unique'); + assert.equal(rarityForRoll(95), 'unique'); + assert.equal(rarityForRoll(96), 'legend'); + assert.equal(rarityForRoll(100), 'legend'); +}); + test('applyDamage: 방어 우선 차감 후 hp', () => { assert.deepEqual(applyDamage(80, 0, 10), { hp: 70, block: 0 }); assert.deepEqual(applyDamage(80, 5, 10), { hp: 75, block: 0 }); diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index cb3754a..d2e9db8 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -3999,9 +3999,20 @@ return pool`, [], 0, 'any'), method('OfferReward', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false) self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false) local pool = self:CardPool() +local byRarity = {} +for _, id in ipairs(pool) do + local r = self.Cards[id].rarity or "normal" + if byRarity[r] == nil then byRarity[r] = {} end + table.insert(byRarity[r], id) +end self.RewardChoices = {} for i = 1, 3 do - self.RewardChoices[i] = pool[math.random(1, #pool)] + local roll = math.random(1, 100) + local want = "normal" + if roll > 95 then want = "legend" elseif roll > 70 then want = "unique" end + local bucket = byRarity[want] + if bucket == nil or #bucket == 0 then bucket = pool end + self.RewardChoices[i] = bucket[math.random(1, #bucket)] self:ApplyRewardVisual(i, self.RewardChoices[i]) end local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")