Merge pull request '복구: codex가 revert한 #96 수정 11개 재통합 + Defend 방어카드 수정 + RULES 경고' (#102) from fix/restore-96-defend into main
This commit was merged in pull request #102.
This commit is contained in:
1
RULES.md
1
RULES.md
@@ -66,6 +66,7 @@ grep -c "CalcPlayerAttack" RootDesk/MyDesk/SlayDeckController.codeblock
|
|||||||
- PR 제목과 본문은 한국어로 작성한다.
|
- PR 제목과 본문은 한국어로 작성한다.
|
||||||
- 산출물 재생성 커밋은 소스 변경 커밋과 분리하거나, 메시지에 "산출물 재생성"을 명시.
|
- 산출물 재생성 커밋은 소스 변경 커밋과 분리하거나, 메시지에 "산출물 재생성"을 명시.
|
||||||
- **PR 머지 후 브랜치 삭제**: 머지된 `feature/*`·`docs/*` 브랜치는 로컬·원격 모두 삭제한다. 삭제 전 `git merge-base --is-ancestor origin/<브랜치> origin/main`로 완전 머지 확인(종료코드 0=완전 머지 → 삭제 가능). main에 없는 커밋이 남은 브랜치와 `codex/*` 등 작업 중 브랜치는 보존한다.
|
- **PR 머지 후 브랜치 삭제**: 머지된 `feature/*`·`docs/*` 브랜치는 로컬·원격 모두 삭제한다. 삭제 전 `git merge-base --is-ancestor origin/<브랜치> origin/main`로 완전 머지 확인(종료코드 0=완전 머지 → 삭제 가능). main에 없는 커밋이 남은 브랜치와 `codex/*` 등 작업 중 브랜치는 보존한다.
|
||||||
|
- **⚠️ main 머지 충돌 시 "머지 전체 revert" 금지 (타인 작업 유실 방지)**: 작업 브랜치에 `git merge main`(또는 origin/main) 했다가 충돌·문제가 나도 **그 머지 커밋을 통째로 `git revert` 하지 말 것.** main에 먼저 들어간 타인의 작업이 collateral로 전부 사라진다. 대신 **소스 충돌만 해소**하고 산출물(codeblock 등)은 **재생성**한다. 충돌이 산출물뿐이면 `git checkout --theirs`/재생성으로 끝. (2026-06-30 사고: codex `#98/#99`가 main 머지 후 그 머지를 revert해 `#96`의 버그수정 11개를 전부 날림 → 다시 재통합해야 했다. 복구는 `git diff <pre-merge> <내브랜치> -- <소스> | git apply --3way` 로 소스만 재적용 후 재생성하면 codex 변경과 충돌 없이 양립.)
|
||||||
|
|
||||||
## 5. 메이커(MSW) 연동 주의
|
## 5. 메이커(MSW) 연동 주의
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@
|
|||||||
"Defend": {
|
"Defend": {
|
||||||
"name": "아이언 바디",
|
"name": "아이언 바디",
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
"kind": "Attack",
|
"kind": "Skill",
|
||||||
"block": 5,
|
"block": 5,
|
||||||
"desc": "방어도 5",
|
"desc": "방어도 5",
|
||||||
"image": "7648c3b8e1ca44fc8ec353561207a670",
|
"image": "7648c3b8e1ca44fc8ec353561207a670",
|
||||||
@@ -89,10 +89,10 @@
|
|||||||
"Rage": {
|
"Rage": {
|
||||||
"name": "분노",
|
"name": "분노",
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
"kind": "Power",
|
"kind": "Attack",
|
||||||
"aoe": true,
|
"aoe": true,
|
||||||
"damage": 4,
|
"damage": 4,
|
||||||
"desc": "매 턴 시작 시 힘 +1",
|
"desc": "모든 적에게 피해를 4 줍니다.",
|
||||||
"image": "379d86e3de064959aa4612f71e84ccfb",
|
"image": "379d86e3de064959aa4612f71e84ccfb",
|
||||||
"class": "warrior",
|
"class": "warrior",
|
||||||
"rarity": "legend"
|
"rarity": "legend"
|
||||||
@@ -605,7 +605,7 @@
|
|||||||
"kind": "Skill",
|
"kind": "Skill",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "normal",
|
"rarity": "normal",
|
||||||
"desc": "카드를 1장 뽑습니다. 카드를 1장 버립니다.",
|
"desc": "카드를 1장 버리고, 이번 턴에 준 피해만큼 방어를 얻습니다.",
|
||||||
"blockPerDamageDealtThisTurn": 1,
|
"blockPerDamageDealtThisTurn": 1,
|
||||||
"discard": 1,
|
"discard": 1,
|
||||||
"image": "c1e19219745e44c39ae6ac2f77e347d9"
|
"image": "c1e19219745e44c39ae6ac2f77e347d9"
|
||||||
@@ -780,7 +780,7 @@
|
|||||||
"kind": "Attack",
|
"kind": "Attack",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "피해를 8 줍니다. 이번 턴에 카드를 사용할 때마다, 대상 적이 체력을 2 잃습니다.",
|
"desc": "피해를 8 줍니다.",
|
||||||
"damage": 8,
|
"damage": 8,
|
||||||
"image": "92a5020c978c46bdabab910598118b86"
|
"image": "92a5020c978c46bdabab910598118b86"
|
||||||
},
|
},
|
||||||
@@ -913,7 +913,7 @@
|
|||||||
"kind": "Skill",
|
"kind": "Skill",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "모든 적에게 부여된 중독과 동일한 만큼의 방어도를 얻습니다. 소멸.",
|
"desc": "카드를 1장 뽑습니다.",
|
||||||
"draw": 1,
|
"draw": 1,
|
||||||
"image": "0946f69d84464df29b24b94c744c868d"
|
"image": "0946f69d84464df29b24b94c744c868d"
|
||||||
},
|
},
|
||||||
@@ -1011,7 +1011,7 @@
|
|||||||
"kind": "Skill",
|
"kind": "Skill",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "unique",
|
"rarity": "unique",
|
||||||
"desc": "교활. 을 얻습니다.",
|
"desc": "교활. 에너지를 1 얻습니다.",
|
||||||
"gainEnergy": 1,
|
"gainEnergy": 1,
|
||||||
"sly": true,
|
"sly": true,
|
||||||
"image": "c1e19219745e44c39ae6ac2f77e347d9"
|
"image": "c1e19219745e44c39ae6ac2f77e347d9"
|
||||||
@@ -1166,7 +1166,7 @@
|
|||||||
"kind": "Skill",
|
"kind": "Skill",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "적이 힘을 X 잃습니다. 약화를 X 부여합니다. 소멸.",
|
"desc": "에너지를 모두 사용하고, 사용한 에너지만큼 적에게 약화를 부여합니다.",
|
||||||
"useAllEnergy": true,
|
"useAllEnergy": true,
|
||||||
"xWeakPerEnergy": 1,
|
"xWeakPerEnergy": 1,
|
||||||
"image": "0946f69d84464df29b24b94c744c868d"
|
"image": "0946f69d84464df29b24b94c744c868d"
|
||||||
@@ -1177,7 +1177,7 @@
|
|||||||
"kind": "Skill",
|
"kind": "Skill",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "를 얻습니다. 카드를 2장 뽑습니다. 소멸.",
|
"desc": "에너지를 1 얻습니다. 카드를 2장 뽑습니다. 소멸.",
|
||||||
"draw": 2,
|
"draw": 2,
|
||||||
"gainEnergy": 1,
|
"gainEnergy": 1,
|
||||||
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
|
||||||
@@ -1251,7 +1251,7 @@
|
|||||||
"kind": "Skill",
|
"kind": "Skill",
|
||||||
"class": "rogue",
|
"class": "rogue",
|
||||||
"rarity": "legend",
|
"rarity": "legend",
|
||||||
"desc": "대상 적에게 소멸된 카드 더미에 있는 모든 표창을 사용합니다.",
|
"desc": "카드를 1장 뽑습니다.",
|
||||||
"draw": 1,
|
"draw": 1,
|
||||||
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
|
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ export function calcAttack(base, str, weak, vulnOnTarget) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function calcEnemyAttack(base, str, weak, vulnOnTarget, strengthLoss = 0) {
|
export function calcEnemyAttack(base, str, weak, vulnOnTarget, strengthLoss = 0) {
|
||||||
return calcAttack(base, Math.max(0, str - strengthLoss), weak, vulnOnTarget);
|
// Lua EnemyActStep 동기화: 힘 손실은 (value+str) 전체에서 차감(음수 힘 허용), 최종 calcAttack이 0 클램프.
|
||||||
|
return calcAttack(base, str - strengthLoss, weak, vulnOnTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 방어 우선 차감 후 hp 적용 → { hp, block }
|
// 방어 우선 차감 후 hp 적용 → { hp, block }
|
||||||
@@ -342,7 +343,7 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
if (c.damagePerDiscardedThisTurn) base += turnDiscardedCards * c.damagePerDiscardedThisTurn;
|
if (c.damagePerDiscardedThisTurn) base += turnDiscardedCards * c.damagePerDiscardedThisTurn;
|
||||||
if (c.damagePerSkillInHand) base += countOtherHandSkills(id) * c.damagePerSkillInHand;
|
if (c.damagePerSkillInHand) base += countOtherHandSkills(id) * c.damagePerSkillInHand;
|
||||||
if (c.damagePerCardDrawnThisCombat) base += cardsDrawnThisCombat * c.damagePerCardDrawnThisCombat;
|
if (c.damagePerCardDrawnThisCombat) base += cardsDrawnThisCombat * c.damagePerCardDrawnThisCombat;
|
||||||
if (c.class === 'Attack' && turnCardsPlayedThisTurn === 0 && c.firstCardDamageBonus) base += c.firstCardDamageBonus;
|
if (c.kind === 'Attack' && turnCardsPlayedThisTurn === 0 && c.firstCardDamageBonus) base += c.firstCardDamageBonus;
|
||||||
if (c.class === 'shiv') {
|
if (c.class === 'shiv') {
|
||||||
if (powerFieldTotal('shivDamageBonus') > 0) base += powerFieldTotal('shivDamageBonus');
|
if (powerFieldTotal('shivDamageBonus') > 0) base += powerFieldTotal('shivDamageBonus');
|
||||||
if (!shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) base += powerFieldTotal('firstShivDamageBonus');
|
if (!shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) base += powerFieldTotal('firstShivDamageBonus');
|
||||||
@@ -422,6 +423,9 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const hitN = (c.hits || 1) + bonusHits;
|
const hitN = (c.hits || 1) + bonusHits;
|
||||||
let useAoe = c.aoe === true;
|
let useAoe = c.aoe === true;
|
||||||
if (c.class === 'shiv' && shivAoeThisCombat === true) useAoe = 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 perHit = calcAttack(baseDamage || 0, pStr, pWeak, 0) * turnAttackMultiplier;
|
||||||
const dealToTarget = (target, amount) => {
|
const dealToTarget = (target, amount) => {
|
||||||
if (!target || !target.alive) return { killed: false, dealt: 0 };
|
if (!target || !target.alive) return { killed: false, dealt: 0 };
|
||||||
@@ -515,9 +519,6 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) {
|
|
||||||
shivFirstDamageBonusUsed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (c.strength) pStr += c.strength;
|
if (c.strength) pStr += c.strength;
|
||||||
@@ -564,7 +565,7 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (c.blockPerDamageDealtThisTurn && c.blockPerDamageDealtThisTurn > 0 && c.kind !== 'Power') {
|
if (c.blockPerDamageDealtThisTurn && c.blockPerDamageDealtThisTurn > 0 && c.kind !== 'Power') {
|
||||||
blockGained += Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn);
|
blockGained += addBlock(Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn));
|
||||||
}
|
}
|
||||||
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained);
|
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained);
|
||||||
}
|
}
|
||||||
@@ -658,7 +659,7 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const baseCost = c.cost || 0;
|
const baseCost = c.cost || 0;
|
||||||
const combatReduction = combatCardCostReduction[id] || 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 cost = handCostZeroThisTurn === true ? 0 : (c.useAllEnergy === true ? energy : (skillFree ? 0 : (c.kind === 'Skill' ? Math.max(0, baseCost - skillCostReductionThisTurn) : baseCost)));
|
||||||
const finalCost = Math.max(0, cost - combatReduction);
|
const finalCost = c.useAllEnergy === true ? cost : Math.max(0, cost - combatReduction);
|
||||||
energy -= finalCost;
|
energy -= finalCost;
|
||||||
resolveCardEffects(id, c, finalCost);
|
resolveCardEffects(id, c, finalCost);
|
||||||
const playedBlock = powerFieldTotal('cardPlayedBlock');
|
const playedBlock = powerFieldTotal('cardPlayedBlock');
|
||||||
@@ -721,7 +722,7 @@ export function simulateCombat(data, rng, stats) {
|
|||||||
const it = m.intents.length ? m.intents[Math.floor(rng() * m.intents.length)] : null;
|
const it = m.intents.length ? m.intents[Math.floor(rng() * m.intents.length)] : null;
|
||||||
if (it) {
|
if (it) {
|
||||||
if (it.kind === 'Attack') {
|
if (it.kind === 'Attack') {
|
||||||
const atk = calcAttack(it.value, Math.max(0, m.str - enemyStrengthLossThisTurn), m.weak, pVuln);
|
const atk = calcEnemyAttack(it.value, m.str, m.weak, pVuln, enemyStrengthLossThisTurn);
|
||||||
const beforeHp = pHp;
|
const beforeHp = pHp;
|
||||||
let incoming = atk;
|
let incoming = atk;
|
||||||
if (pIntangible > 0 && incoming > 1) incoming = 1;
|
if (pIntangible > 0 && incoming > 1) incoming = 1;
|
||||||
|
|||||||
@@ -262,6 +262,19 @@ test('simulateCombat: 카드 취약 부여가 같은 카드 피해에 선적용
|
|||||||
assert.equal(r.turns, 1);
|
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(매턴 힘) 누적', () => {
|
test('simulateCombat: Power(매턴 힘) 누적', () => {
|
||||||
const data = {
|
const data = {
|
||||||
cards: {
|
cards: {
|
||||||
@@ -882,6 +895,44 @@ test("calcEnemyAttack: enemyStrengthLossThisTurn reduces enemy attack damage", (
|
|||||||
assert.equal(calcEnemyAttack(10, 6, 0, 0, 0), 16);
|
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", () => {
|
test("simulateCombat: repeatOnKill repeats an attack until no kill occurs", () => {
|
||||||
const shared = {
|
const shared = {
|
||||||
cards: {
|
cards: {
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ if self.HandCostZeroThisTurn == true then
|
|||||||
elseif c.useAllEnergy == true then
|
elseif c.useAllEnergy == true then
|
||||||
cost = self.Energy
|
cost = self.Energy
|
||||||
end
|
end
|
||||||
if c.kind == "Skill" and self.NextSkillCostZero == true then
|
if c.kind == "Skill" and c.useAllEnergy ~= true and self.NextSkillCostZero == true then
|
||||||
cost = 0
|
cost = 0
|
||||||
skillFree = true
|
skillFree = true
|
||||||
end
|
end
|
||||||
if c.kind == "Skill" and self.SkillCostReductionThisTurn ~= nil and self.SkillCostReductionThisTurn > 0 then
|
if c.kind == "Skill" and c.useAllEnergy ~= true and self.SkillCostReductionThisTurn ~= nil and self.SkillCostReductionThisTurn > 0 then
|
||||||
cost = math.max(0, cost - self.SkillCostReductionThisTurn)
|
cost = math.max(0, cost - self.SkillCostReductionThisTurn)
|
||||||
end
|
end
|
||||||
if self.CombatCardCostReduction ~= nil and self.CombatCardCostReduction[cardId] ~= nil then
|
if c.useAllEnergy ~= true and self.CombatCardCostReduction ~= nil and self.CombatCardCostReduction[cardId] ~= nil then
|
||||||
cost = math.max(0, cost - self.CombatCardCostReduction[cardId])
|
cost = math.max(0, cost - self.CombatCardCostReduction[cardId])
|
||||||
end
|
end
|
||||||
if c.kind == "Skill" and self.NextSkillRepeatCount ~= nil and self.NextSkillRepeatCount > 0 then
|
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]
|
local m = self.Monsters[i]
|
||||||
if m ~= nil and m.alive == true then
|
if m ~= nil and m.alive == true then
|
||||||
local dmg = amount
|
local dmg = amount
|
||||||
if m.vuln > 0 then
|
if isAttack == true and m.vuln > 0 then
|
||||||
dmg = math.floor(dmg * 1.5)
|
dmg = math.floor(dmg * 1.5)
|
||||||
end
|
end
|
||||||
if m.block > 0 then
|
if m.block > 0 then
|
||||||
@@ -392,6 +392,12 @@ for i = 1, #self.Monsters do
|
|||||||
m.hp = m.hp - dmg
|
m.hp = m.hp - dmg
|
||||||
if dmg > 0 then
|
if dmg > 0 then
|
||||||
self.DamageDealtThisTurn = (self.DamageDealtThisTurn or 0) + dmg
|
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
|
end
|
||||||
self:ShowDmgPop(i, dmg)
|
self:ShowDmgPop(i, dmg)
|
||||||
self:MonsterHitMotion(i)
|
self:MonsterHitMotion(i)
|
||||||
@@ -409,6 +415,7 @@ self:RenderCombat()
|
|||||||
self:CheckCombatEnd()
|
self:CheckCombatEnd()
|
||||||
return killCount > 0`, [
|
return killCount > 0`, [
|
||||||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||||||
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'isAttack' },
|
||||||
], 0, 'boolean'),
|
], 0, 'boolean'),
|
||||||
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
|
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
|
||||||
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
||||||
@@ -682,7 +689,10 @@ self.NextTurnAddCards = {}
|
|||||||
self:UpdateDiscardPrompt()
|
self:UpdateDiscardPrompt()
|
||||||
self:RenderHand(false)
|
self:RenderHand(false)
|
||||||
self:RenderPiles()`),
|
self:RenderPiles()`),
|
||||||
method('CheckCombatEnd', `local anyAlive = false
|
method('CheckCombatEnd', `if self.CombatOver == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local anyAlive = false
|
||||||
for i = 1, #self.Monsters do
|
for i = 1, #self.Monsters do
|
||||||
if self.Monsters[i].alive == true then anyAlive = true; break end
|
if self.Monsters[i].alive == true then anyAlive = true; break end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ for i = #list, 2, -1 do
|
|||||||
\tlocal j = math.random(1, i)
|
\tlocal j = math.random(1, i)
|
||||||
\tlist[i], list[j] = list[j], list[i]
|
\tlist[i], list[j] = list[j], list[i]
|
||||||
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]),
|
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]),
|
||||||
method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton")
|
method('BindButtons', `if self.ButtonsBound == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.ButtonsBound = true
|
||||||
|
local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton")
|
||||||
if endTurn ~= nil and (endTurn.ButtonComponent ~= nil or endTurn:AddComponent("ButtonComponent") ~= nil) then
|
if endTurn ~= nil and (endTurn.ButtonComponent ~= nil or endTurn:AddComponent("ButtonComponent") ~= nil) then
|
||||||
if self.EndTurnHandler ~= nil then
|
if self.EndTurnHandler ~= nil then
|
||||||
endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)
|
endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)
|
||||||
@@ -471,6 +475,7 @@ for i = 1, amount do
|
|||||||
\tlocal cardId = table.remove(self.DrawPile)
|
\tlocal cardId = table.remove(self.DrawPile)
|
||||||
\ttable.insert(drawnCards, cardId)
|
\ttable.insert(drawnCards, cardId)
|
||||||
\tself.CardsDrawnThisCombat = (self.CardsDrawnThisCombat or 0) + 1
|
\tself.CardsDrawnThisCombat = (self.CardsDrawnThisCombat or 0) + 1
|
||||||
|
\tself:ApplyDrawTrigger()
|
||||||
\tif #self.Hand >= 10 then
|
\tif #self.Hand >= 10 then
|
||||||
\t\ttable.insert(self.DiscardPile, cardId)
|
\t\ttable.insert(self.DiscardPile, cardId)
|
||||||
\t\tself:TriggerSly(cardId)
|
\t\tself:TriggerSly(cardId)
|
||||||
|
|||||||
@@ -3,6 +3,42 @@ 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';
|
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 = [
|
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
|
method('GetHandSlotX', `local n = 0
|
||||||
if self.Hand ~= nil then
|
if self.Hand ~= nil then
|
||||||
n = #self.Hand
|
n = #self.Hand
|
||||||
@@ -311,7 +347,7 @@ end
|
|||||||
if c.damagePerCardDrawnThisCombat ~= nil then
|
if c.damagePerCardDrawnThisCombat ~= nil then
|
||||||
base2 = base2 + (self.CardsDrawnThisCombat or 0) * c.damagePerCardDrawnThisCombat
|
base2 = base2 + (self.CardsDrawnThisCombat or 0) * c.damagePerCardDrawnThisCombat
|
||||||
end
|
end
|
||||||
if c.class == "Attack" and (self.TurnCardsPlayedThisTurn or 0) == 0 and c.firstCardDamageBonus ~= nil then
|
if c.kind == "Attack" and (self.TurnCardsPlayedThisTurn or 0) == 0 and c.firstCardDamageBonus ~= nil then
|
||||||
base2 = base2 + c.firstCardDamageBonus
|
base2 = base2 + c.firstCardDamageBonus
|
||||||
end
|
end
|
||||||
if c.class == "shiv" then
|
if c.class == "shiv" then
|
||||||
@@ -506,7 +542,7 @@ if c.kind == "Attack" then
|
|||||||
local function resolveAttackRound()
|
local function resolveAttackRound()
|
||||||
local roundKilled = false
|
local roundKilled = false
|
||||||
if useAoe == true then
|
if useAoe == true then
|
||||||
local killed = self:DealDamageToAllMonsters(total)
|
local killed = self:DealDamageToAllMonsters(total, true)
|
||||||
if killed == true then roundKilled = true end
|
if killed == true then roundKilled = true end
|
||||||
elseif c.randomTargetEachHit == true then
|
elseif c.randomTargetEachHit == true then
|
||||||
for h = 1, hitN do
|
for h = 1, hitN do
|
||||||
@@ -681,39 +717,6 @@ if c.drawSkillBlock ~= nil and c.drawSkillBlock > 0 then
|
|||||||
end
|
end
|
||||||
end
|
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
|
if c.addShiv ~= nil and c.discard == nil and c.discardAll ~= true then
|
||||||
self:AddCardsToHand("Shiv", c.addShiv)
|
self:AddCardsToHand("Shiv", c.addShiv)
|
||||||
end`, [
|
end`, [
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ function writeCodeblocks() {
|
|||||||
prop('any', 'AllDeckCloseHandler'),
|
prop('any', 'AllDeckCloseHandler'),
|
||||||
prop('number', 'SoulPoints', '0'),
|
prop('number', 'SoulPoints', '0'),
|
||||||
prop('boolean', 'LobbyBound', 'false'),
|
prop('boolean', 'LobbyBound', 'false'),
|
||||||
|
prop('boolean', 'ButtonsBound', 'false'),
|
||||||
prop('number', 'LobbyTpTries', '0'),
|
prop('number', 'LobbyTpTries', '0'),
|
||||||
prop('boolean', 'CodexMode', 'false'),
|
prop('boolean', 'CodexMode', 'false'),
|
||||||
prop('any', 'CodexCards'),
|
prop('any', 'CodexCards'),
|
||||||
|
|||||||
Reference in New Issue
Block a user