Merge pull request '표창 카드 손패 생성 구현' (#69) from codex/implement-shuriken-cards into main
Reviewed-on: #69
This commit was merged in pull request #69.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -417,6 +417,17 @@
|
||||
"desc": "피해를 6 줍니다.",
|
||||
"damage": 6
|
||||
},
|
||||
"Shiv": {
|
||||
"name": "표창",
|
||||
"cost": 0,
|
||||
"kind": "Attack",
|
||||
"class": "shiv",
|
||||
"rarity": "normal",
|
||||
"desc": "피해를 4 줍니다. 소멸.",
|
||||
"damage": 4,
|
||||
"exhaust": true,
|
||||
"token": true
|
||||
},
|
||||
"DaggerSpray": {
|
||||
"name": "단검 분사",
|
||||
"cost": 1,
|
||||
@@ -465,8 +476,9 @@
|
||||
"kind": "Attack",
|
||||
"class": "bandit",
|
||||
"rarity": "normal",
|
||||
"desc": "피해를 3 줍니다. 단도를 2장 손으로 가져옵니다.",
|
||||
"damage": 3
|
||||
"desc": "피해를 3 줍니다. 표창을 2장 손으로 가져옵니다.",
|
||||
"damage": 3,
|
||||
"addShiv": 2
|
||||
},
|
||||
"FollowThrough": {
|
||||
"name": "완수",
|
||||
@@ -530,12 +542,12 @@
|
||||
"BladeDance": {
|
||||
"name": "검무",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "normal",
|
||||
"desc": "단도를 3장 손으로 가져옵니다. 소멸.",
|
||||
"damage": 4,
|
||||
"hits": 3
|
||||
"desc": "표창을 3장 손으로 가져옵니다. 소멸.",
|
||||
"addShiv": 3,
|
||||
"exhaust": true
|
||||
},
|
||||
"Backflip": {
|
||||
"name": "공중제비",
|
||||
@@ -568,13 +580,12 @@
|
||||
"CloakAndDagger": {
|
||||
"name": "망토와 단검",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "normal",
|
||||
"desc": "방어도를 6 얻습니다. 단도를 1장 손으로 가져옵니다.",
|
||||
"desc": "방어도를 6 얻습니다. 표창을 1장 손으로 가져옵니다.",
|
||||
"block": 6,
|
||||
"damage": 4,
|
||||
"hits": 1
|
||||
"addShiv": 1
|
||||
},
|
||||
"DeadlyPoison": {
|
||||
"name": "맹독",
|
||||
@@ -727,13 +738,12 @@
|
||||
"HiddenDaggers": {
|
||||
"name": "숨겨진 단검",
|
||||
"cost": 0,
|
||||
"kind": "Attack",
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "unique",
|
||||
"desc": "카드를 2장 버립니다. 단도를 2장 손으로 가져옵니다.",
|
||||
"damage": 4,
|
||||
"hits": 2,
|
||||
"discard": 2
|
||||
"desc": "카드를 2장 버립니다. 표창을 2장 손으로 가져옵니다.",
|
||||
"discard": 2,
|
||||
"addShiv": 2
|
||||
},
|
||||
"EscapePlan": {
|
||||
"name": "탈출구",
|
||||
@@ -813,12 +823,11 @@
|
||||
"UpMySleeve": {
|
||||
"name": "비책",
|
||||
"cost": 2,
|
||||
"kind": "Attack",
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "unique",
|
||||
"desc": "단도를 3장 손으로 가져옵니다. 이 카드의 비용이 1 감소합니다.",
|
||||
"damage": 4,
|
||||
"hits": 3
|
||||
"desc": "표창을 3장 손으로 가져옵니다. 이 카드의 비용이 1 감소합니다.",
|
||||
"addShiv": 3
|
||||
},
|
||||
"BouncingFlask": {
|
||||
"name": "탄성 플라스크",
|
||||
@@ -873,12 +882,11 @@
|
||||
"InfiniteBlades": {
|
||||
"name": "무한의 검날",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"kind": "Power",
|
||||
"class": "bandit",
|
||||
"rarity": "unique",
|
||||
"desc": "내 턴 시작 시, 단도를 1장 손으로 가져옵니다.",
|
||||
"damage": 4,
|
||||
"hits": 1
|
||||
"desc": "내 턴 시작 시, 표창을 1장 손으로 가져옵니다.",
|
||||
"turnStartShiv": 1
|
||||
},
|
||||
"Footwork": {
|
||||
"name": "발놀림",
|
||||
@@ -918,7 +926,7 @@
|
||||
"kind": "Power",
|
||||
"class": "bandit",
|
||||
"rarity": "unique",
|
||||
"desc": "단도의 피해량이 4 증가합니다.",
|
||||
"desc": "표창의 피해량이 4 증가합니다.",
|
||||
"powerEffect": "strengthPerTurn",
|
||||
"value": 1
|
||||
},
|
||||
@@ -928,7 +936,7 @@
|
||||
"kind": "Power",
|
||||
"class": "bandit",
|
||||
"rarity": "unique",
|
||||
"desc": "단도가 보존을 얻습니다. 매 턴마다 처음으로 사용하는 단도의 피해량이 9 증가합니다.",
|
||||
"desc": "표창이 보존을 얻습니다. 매 턴마다 처음으로 사용하는 표창의 피해량이 9 증가합니다.",
|
||||
"powerEffect": "strengthPerTurn",
|
||||
"value": 1
|
||||
},
|
||||
@@ -1018,9 +1026,9 @@
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "legend",
|
||||
"desc": "손에 있는 모든 카드를 버립니다. 버린 카드의 수만큼 단도를 손으로 가져옵니다.",
|
||||
"draw": 1,
|
||||
"discardAll": true
|
||||
"desc": "손에 있는 모든 카드를 버립니다. 버린 카드의 수만큼 표창을 손으로 가져옵니다.",
|
||||
"discardAll": true,
|
||||
"addShivPerDiscard": true
|
||||
},
|
||||
"ShadowStep": {
|
||||
"name": "그림자 걸음",
|
||||
@@ -1053,12 +1061,11 @@
|
||||
"BladeOfInk": {
|
||||
"name": "잉크 칼날",
|
||||
"cost": 1,
|
||||
"kind": "Attack",
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "legend",
|
||||
"desc": "잉크투성이 단도를 2장 손으로 가져옵니다.",
|
||||
"damage": 4,
|
||||
"hits": 2
|
||||
"desc": "잉크투성이 표창을 2장 손으로 가져옵니다.",
|
||||
"addShiv": 2
|
||||
},
|
||||
"Burst": {
|
||||
"name": "폭주",
|
||||
@@ -1075,7 +1082,7 @@
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "legend",
|
||||
"desc": "대상 적에게 소멸된 카드 더미에 있는 모든 단도를 사용합니다.",
|
||||
"desc": "대상 적에게 소멸된 카드 더미에 있는 모든 표창을 사용합니다.",
|
||||
"draw": 1
|
||||
},
|
||||
"BulletTime": {
|
||||
@@ -1164,14 +1171,13 @@
|
||||
"FanOfKnives": {
|
||||
"name": "칼날 부채",
|
||||
"cost": 2,
|
||||
"kind": "Attack",
|
||||
"kind": "Skill",
|
||||
"class": "bandit",
|
||||
"rarity": "legend",
|
||||
"desc": "단도가 이제 모든 적을 대상으로 합니다. 단도를 4장 손으로 가져옵니다.",
|
||||
"desc": "표창이 이제 모든 적을 대상으로 합니다. 표창을 4장 손으로 가져옵니다.",
|
||||
"powerEffect": "strengthPerTurn",
|
||||
"value": 1,
|
||||
"damage": 4,
|
||||
"hits": 4
|
||||
"addShiv": 4
|
||||
},
|
||||
"SerpentForm": {
|
||||
"name": "구렁이의 형상",
|
||||
|
||||
@@ -131,6 +131,12 @@ export function simulateCombat(data, rng, stats) {
|
||||
} else hand.push(card);
|
||||
}
|
||||
}
|
||||
function addCardsToHand(id, n) {
|
||||
for (let k = 0; k < n; k++) {
|
||||
if (hand.length >= 10) discard.push(id);
|
||||
else hand.push(id);
|
||||
}
|
||||
}
|
||||
const aliveList = () => mob.filter((m) => m.alive);
|
||||
function resolveCardEffects(id, c, costSpent, recordStats = true) {
|
||||
const alive = aliveList();
|
||||
@@ -166,7 +172,7 @@ export function simulateCombat(data, rng, stats) {
|
||||
}
|
||||
if (c.block) { blockGained = Math.max(0, c.block + pDex); pBlock += blockGained; }
|
||||
} else if (c.kind === 'Power') {
|
||||
if (c.powerEffect && recordStats) powers.push(id);
|
||||
if (recordStats) powers.push(id);
|
||||
} else {
|
||||
if (c.block) { blockGained = Math.max(0, c.block + pDex); pBlock += blockGained; }
|
||||
if ((c.weak || c.vuln || c.poison) && alive.length) {
|
||||
@@ -182,6 +188,7 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (c.selfVuln) pVuln += c.selfVuln;
|
||||
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
|
||||
if (c.draw) draw(c.draw);
|
||||
if (c.addShiv && !c.discard && c.discardAll !== true) addCardsToHand('Shiv', c.addShiv);
|
||||
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained);
|
||||
}
|
||||
function triggerSly(id) {
|
||||
@@ -196,12 +203,15 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (trigger) triggerSly(id);
|
||||
}
|
||||
function applyDiscardEffects(c) {
|
||||
let discarded = 0;
|
||||
if (c.discardAll) {
|
||||
while (hand.length) discardHandCard(hand.length - 1, true);
|
||||
while (hand.length) { discardHandCard(hand.length - 1, true); discarded++; }
|
||||
} else if (c.discard) {
|
||||
const n = Math.min(c.discard, hand.length);
|
||||
for (let i = 0; i < n; i++) discardHandCard(hand.length - 1, true);
|
||||
for (let i = 0; i < n; i++) { discardHandCard(hand.length - 1, true); discarded++; }
|
||||
}
|
||||
if (c.addShiv && (c.discard || c.discardAll === true)) addCardsToHand('Shiv', c.addShiv);
|
||||
if (c.addShivPerDiscard === true) addCardsToHand('Shiv', discarded);
|
||||
}
|
||||
|
||||
while (turns < MAX_TURNS) {
|
||||
@@ -215,6 +225,7 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (pc.powerEffect === 'strengthPerTurn') pStr += pc.value;
|
||||
else if (pc.powerEffect === 'energyPerTurn') energyBonus += pc.value;
|
||||
else if (pc.powerEffect === 'blockPerTurn') pBlock += pc.value;
|
||||
if (pc.turnStartShiv) addCardsToHand('Shiv', pc.turnStartShiv);
|
||||
}
|
||||
let energy = ENERGY + energyBonus; draw(HAND_SIZE);
|
||||
while (true) {
|
||||
|
||||
@@ -447,3 +447,17 @@ test("simulateCombat: thorns reflects unblocked attack damage", () => {
|
||||
assert.equal(r.turns, 1);
|
||||
assert.equal(r.playerHpRemaining, 79);
|
||||
});
|
||||
|
||||
test("simulateCombat: addShiv creates shuriken cards in hand", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
MakeShiv: { name: "MakeShiv", cost: 0, kind: "Skill", addShiv: 2 },
|
||||
Shiv: { name: "표창", cost: 0, kind: "Attack", damage: 4, exhaust: true },
|
||||
},
|
||||
starterDeck: ["MakeShiv"],
|
||||
monsters: [{ name: "Dummy", maxHp: 8, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
@@ -173,12 +173,16 @@ function luaCardsTable(cards) {
|
||||
if (c.poison != null) fields.push(`poison = ${c.poison}`);
|
||||
if (c.discard != null) fields.push(`discard = ${c.discard}`);
|
||||
if (c.discardAll === true) fields.push('discardAll = true');
|
||||
if (c.addShiv != null) fields.push(`addShiv = ${c.addShiv}`);
|
||||
if (c.turnStartShiv != null) fields.push(`turnStartShiv = ${c.turnStartShiv}`);
|
||||
if (c.addShivPerDiscard === true) fields.push('addShivPerDiscard = true');
|
||||
if (c.sly === true) fields.push('sly = true');
|
||||
if (c.retain === true) fields.push('retain = true');
|
||||
if (c.exhaust === true || String(c.desc || '').includes('소멸.')) fields.push('exhaust = true');
|
||||
if (c.aoe === true) fields.push('aoe = true');
|
||||
if (c.unplayable === true) fields.push('unplayable = true');
|
||||
if (c.curse === true) fields.push('curse = true');
|
||||
if (c.token === true) fields.push('token = true');
|
||||
if (c.endTurnDamage != null) fields.push(`endTurnDamage = ${c.endTurnDamage}`);
|
||||
if (c.fx != null) fields.push(`fx = ${luaStr(c.fx)}`);
|
||||
if (c.image != null) fields.push(`image = ${luaStr(c.image)}`);
|
||||
@@ -2984,6 +2988,8 @@ function writeCodeblocks() {
|
||||
prop('string', 'PlayerJob', '""'),
|
||||
prop('number', 'DiscardSelectRemaining', '0'),
|
||||
prop('number', 'DiscardSelectTotal', '0'),
|
||||
prop('number', 'DiscardPostShiv', '0'),
|
||||
prop('number', 'DiscardShivPerPick', '0'),
|
||||
], [
|
||||
method('OnBeginPlay', `${luaCardsTable(CARDS.cards)}
|
||||
${luaFramesTable()}
|
||||
@@ -3474,6 +3480,8 @@ self.FirstHpLossDone = false
|
||||
self.ClayBlockNext = 0
|
||||
self.DiscardSelectRemaining = 0
|
||||
self.DiscardSelectTotal = 0
|
||||
self.DiscardPostShiv = 0
|
||||
self.DiscardShivPerPick = 0
|
||||
self.CombatOver = false
|
||||
self.DiscardPile = {}
|
||||
self.ExhaustPile = {}
|
||||
@@ -3827,6 +3835,9 @@ if self.PlayerPowers ~= nil then
|
||||
elseif pc.powerEffect == "blockPerTurn" then
|
||||
self.PlayerBlock = self.PlayerBlock + pc.value
|
||||
end
|
||||
if pc.turnStartShiv ~= nil then
|
||||
self:AddCardsToHand("Shiv", pc.turnStartShiv)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3898,6 +3909,24 @@ end`, [
|
||||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' },
|
||||
]),
|
||||
method('AddCardsToHand', `if self.Hand == nil then
|
||||
self.Hand = {}
|
||||
end
|
||||
if self.DiscardPile == nil then
|
||||
self.DiscardPile = {}
|
||||
end
|
||||
for i = 1, amount do
|
||||
if #self.Hand >= 10 then
|
||||
table.insert(self.DiscardPile, cardId)
|
||||
else
|
||||
table.insert(self.Hand, cardId)
|
||||
end
|
||||
end
|
||||
self:RenderHand(false)
|
||||
self:RenderPiles()`, [
|
||||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||
]),
|
||||
method('RecycleDiscardIntoDraw', `if self.DiscardPile == nil or #self.DiscardPile <= 0 then
|
||||
\treturn
|
||||
end
|
||||
@@ -4394,7 +4423,7 @@ elseif c.kind == "Skill" then
|
||||
self:AddCardBlock(c.block)
|
||||
end
|
||||
elseif c.kind == "Power" then
|
||||
if c.powerEffect ~= nil and free ~= true then
|
||||
if free ~= true then
|
||||
table.insert(self.PlayerPowers, cardId)
|
||||
end
|
||||
end
|
||||
@@ -4433,6 +4462,9 @@ if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then
|
||||
end
|
||||
if c.draw ~= nil then
|
||||
self:DrawCards(c.draw, true)
|
||||
end
|
||||
if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then
|
||||
self:AddCardsToHand("Shiv", c.addShiv)
|
||||
end`, [
|
||||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
||||
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' },
|
||||
@@ -4485,12 +4517,26 @@ if n <= 0 then
|
||||
end
|
||||
self.DiscardSelectRemaining = n
|
||||
self.DiscardSelectTotal = n
|
||||
self.DiscardPostShiv = 0
|
||||
self.DiscardShivPerPick = 0
|
||||
if c.addShiv ~= nil then
|
||||
self.DiscardPostShiv = c.addShiv
|
||||
end
|
||||
if c.addShivPerDiscard == true then
|
||||
self.DiscardShivPerPick = 1
|
||||
end
|
||||
self:UpdateDiscardPrompt()
|
||||
self:Toast("버릴 카드를 선택하세요")
|
||||
return true`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'c' }], 0, 'boolean'),
|
||||
method('FinishDiscardSelection', `self.DiscardSelectRemaining = 0
|
||||
self.DiscardSelectTotal = 0
|
||||
local shivCount = self.DiscardPostShiv or 0
|
||||
self.DiscardPostShiv = 0
|
||||
self.DiscardShivPerPick = 0
|
||||
self:UpdateDiscardPrompt()
|
||||
if shivCount > 0 then
|
||||
self:AddCardsToHand("Shiv", shivCount)
|
||||
end
|
||||
self:RenderHand(false)
|
||||
self:RenderPiles()
|
||||
self:RenderCombat()
|
||||
@@ -4501,7 +4547,11 @@ end
|
||||
if self.Hand == nil or self.Hand[slot] == nil then
|
||||
return true
|
||||
end
|
||||
local discarded = self.Hand[slot]
|
||||
self:DiscardHandCard(slot, true)
|
||||
if discarded ~= nil and self.DiscardShivPerPick ~= nil and self.DiscardShivPerPick > 0 then
|
||||
self.DiscardPostShiv = (self.DiscardPostShiv or 0) + self.DiscardShivPerPick
|
||||
end
|
||||
self.DiscardSelectRemaining = self.DiscardSelectRemaining - 1
|
||||
if self.DiscardSelectRemaining <= 0 or #self.Hand <= 0 then
|
||||
self:FinishDiscardSelection()
|
||||
@@ -4924,6 +4974,8 @@ self.ExhaustPile = {}
|
||||
self.Hand = {}
|
||||
self.DiscardSelectRemaining = 0
|
||||
self.DiscardSelectTotal = 0
|
||||
self.DiscardPostShiv = 0
|
||||
self.DiscardShivPerPick = 0
|
||||
self:UpdateDiscardPrompt()
|
||||
self:RenderHand(false)
|
||||
self:RenderPiles()`),
|
||||
@@ -5392,7 +5444,7 @@ self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", floorText)
|
||||
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
|
||||
method('CardPool', `local pool = {}
|
||||
for id, c in pairs(self.Cards) do
|
||||
if c.class == self.SelectedClass or (self.PlayerJob ~= "" and c.class == self.PlayerJob) then
|
||||
if c.token ~= true and (c.class == self.SelectedClass or (self.PlayerJob ~= "" and c.class == self.PlayerJob)) then
|
||||
table.insert(pool, id)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user