feat: implement compound molt armor mechanics and update balance

- Add permanent +1 baseArmor bonus on each molt to enable compound armor growth across multiple molts
- Track molt activation with _moltZeroed flag to ensure armor restoration only occurs after a molt event
- Extend protect/fortify skills to also boost baseArmor when applied after a molt (to preserve interim boosts)
- Update card venom values for progressive difficulty curve
- Adjust mission rewards and replace bt_hemomancer_1 with bt_hemorrhage_1 in reward pool
- Add 23 new mission briefing videos (mission_001 through mission_023)

This refines the molt mechanic to support long-term armor progression while maintaining balance across campaigns.
This commit is contained in:
Brian Fertig 2026-03-27 20:30:56 -06:00
parent ab4225bd42
commit fe229d7462
29 changed files with 66 additions and 50 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7009,7 +7009,7 @@
}, },
{ {
"name": "venom", "name": "venom",
"value": 2, "value": 4,
"trigger": "on_attack" "trigger": "on_attack"
} }
] ]
@ -7027,7 +7027,7 @@
}, },
{ {
"name": "venom", "name": "venom",
"value": 4, "value": 5,
"trigger": "on_attack" "trigger": "on_attack"
} }
] ]
@ -7045,7 +7045,7 @@
}, },
{ {
"name": "venom", "name": "venom",
"value": 4, "value": 7,
"trigger": "on_attack" "trigger": "on_attack"
} }
] ]
@ -7063,7 +7063,7 @@
}, },
{ {
"name": "venom", "name": "venom",
"value": 6, "value": 10,
"trigger": "on_attack" "trigger": "on_attack"
} }
] ]
@ -7081,7 +7081,7 @@
}, },
{ {
"name": "venom", "name": "venom",
"value": 10, "value": 14,
"trigger": "on_attack" "trigger": "on_attack"
} }
] ]

View File

@ -240,7 +240,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 700, "gold": 600,
"cards": [ "cards": [
"bt_ghoul_1" "bt_ghoul_1"
] ]
@ -268,7 +268,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 800, "gold": 600,
"cards": [ "cards": [
"bt_revenant_1" "bt_revenant_1"
] ]
@ -297,7 +297,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 900, "gold": 600,
"cards": [ "cards": [
"bt_bloodmage_1" "bt_bloodmage_1"
] ]
@ -321,12 +321,12 @@
"bt_harbinger_1", "bt_harbinger_1",
"bt_vampire_1", "bt_vampire_1",
"bt_bloodlord_1", "bt_bloodlord_1",
"bt_hemomancer_1", "bt_hemorrhage_1",
"bt_crimsonguard_1" "bt_crimsonguard_1"
] ]
}, },
"rewards": { "rewards": {
"gold": 1000, "gold": 600,
"cards": [ "cards": [
"bt_harbinger_1" "bt_harbinger_1"
] ]
@ -355,7 +355,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1100, "gold": 1000,
"cards": [ "cards": [
"bt_bloodlord_1" "bt_bloodlord_1"
] ]
@ -381,7 +381,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1200, "gold": 600,
"cards": [ "cards": [
"xeno_drone_1" "xeno_drone_1"
] ]
@ -408,7 +408,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1300, "gold": 600,
"cards": [ "cards": [
"xeno_spitter_1" "xeno_spitter_1"
] ]
@ -435,7 +435,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1400, "gold": 600,
"cards": [ "cards": [
"xeno_beetle_1" "xeno_beetle_1"
] ]
@ -463,7 +463,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1500, "gold": 800,
"cards": [ "cards": [
"xeno_worker_1" "xeno_worker_1"
] ]
@ -491,7 +491,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1600, "gold": 600,
"cards": [ "cards": [
"xeno_guard_1" "xeno_guard_1"
] ]
@ -519,7 +519,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1700, "gold": 600,
"cards": [ "cards": [
"xeno_mantis_1" "xeno_mantis_1"
] ]
@ -547,7 +547,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1800, "gold": 600,
"cards": [ "cards": [
"xeno_horror_1" "xeno_horror_1"
] ]
@ -575,7 +575,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 1900, "gold": 1200,
"cards": [ "cards": [
"xeno_hiveguard_1" "xeno_hiveguard_1"
] ]
@ -602,7 +602,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2000, "gold": 700,
"cards": [ "cards": [
"right_acolyte_1" "right_acolyte_1"
] ]
@ -629,7 +629,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2100, "gold": 700,
"cards": [ "cards": [
"right_pilgrim_1" "right_pilgrim_1"
] ]
@ -656,7 +656,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2200, "gold": 700,
"cards": [ "cards": [
"right_sentinel_1" "right_sentinel_1"
] ]
@ -683,7 +683,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2300, "gold": 700,
"cards": [ "cards": [
"right_hacker_1" "right_hacker_1"
] ]
@ -710,7 +710,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2400, "gold": 700,
"cards": [ "cards": [
"right_zealot_1" "right_zealot_1"
] ]
@ -737,7 +737,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2500, "gold": 700,
"cards": [ "cards": [
"right_templar_1" "right_templar_1"
] ]
@ -762,7 +762,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2600, "gold": 700,
"cards": [ "cards": [
"right_purifier_1" "right_purifier_1"
] ]
@ -789,7 +789,7 @@
] ]
}, },
"rewards": { "rewards": {
"gold": 2700, "gold": 1300,
"cards": [ "cards": [
"right_prophet_1" "right_prophet_1"
] ]

View File

@ -239,6 +239,7 @@ export class CombatEngine {
[commander, ...cards], [enemyCommander, ...enemyLanes], ctx); [commander, ...cards], [enemyCommander, ...enemyLanes], ctx);
if (ctx.moltHeal > 0) { if (ctx.moltHeal > 0) {
this._log(`${card.name} molts: heals ${ctx.moltHeal}, loses ${ctx.moltArmorLost} armor`); this._log(`${card.name} molts: heals ${ctx.moltHeal}, loses ${ctx.moltArmorLost} armor`);
card._moltZeroed = true;
this.events.push({ type: 'molt', card, heal: ctx.moltHeal, armorLost: ctx.moltArmorLost }); this.events.push({ type: 'molt', card, heal: ctx.moltHeal, armorLost: ctx.moltArmorLost });
} }
} }
@ -986,13 +987,15 @@ export class CombatEngine {
} }
} }
// Molt preAttack restore: if attacker has molt and armor was zeroed by molt this // Molt preAttack restore: if molt fired this preBattle, restore armor to (baseArmor + 1).
// preBattle, restore it to (baseArmor + 1) before the attack so the card fights // baseArmor reflects any interim armor boosts (e.g. protect) that fired while armor was zeroed.
// with a fresh shell. Condition: molt skill present, armor currently 0, base armor > 0. // The +1 is permanently added to baseArmor so it compounds over multiple molts.
const moltSkill = pending.attacker.skills.find(s => s.name === 'molt'); const moltSkill = pending.attacker.skills.find(s => s.name === 'molt');
if (moltSkill && pending.attacker.currentArmor === 0 && (pending.attacker.baseArmor ?? 0) > 0) { if (moltSkill && pending.attacker._moltZeroed) {
const armorGain = (pending.attacker.baseArmor ?? 0) + 1; const armorGain = (pending.attacker.baseArmor ?? 0) + 1;
pending.attacker.currentArmor = armorGain; pending.attacker.currentArmor = armorGain;
pending.attacker.baseArmor = armorGain;
pending.attacker._moltZeroed = false;
preAttackFires.push({ skill: 'moltRestore', card: pending.attacker, armorGain }); preAttackFires.push({ skill: 'moltRestore', card: pending.attacker, armorGain });
this._log(`${pending.attacker.name} molt restore: +${armorGain} ARM before attacking`); this._log(`${pending.attacker.name} molt restore: +${armorGain} ARM before attacking`);
} }
@ -1094,6 +1097,7 @@ export class CombatEngine {
for (const buff of card._tempBuffs) { for (const buff of card._tempBuffs) {
if (buff.stat === 'currentAttack') card.currentAttack = Math.max(0, card.currentAttack - buff.amount); if (buff.stat === 'currentAttack') card.currentAttack = Math.max(0, card.currentAttack - buff.amount);
if (buff.stat === 'currentArmor') card.currentArmor = Math.max(0, card.currentArmor - buff.amount); if (buff.stat === 'currentArmor') card.currentArmor = Math.max(0, card.currentArmor - buff.amount);
if (buff.stat === 'baseArmor') card.baseArmor = Math.max(0, (card.baseArmor ?? 0) - buff.amount);
if (buff.stat === 'skillValues') { if (buff.stat === 'skillValues') {
for (const { index, amount } of buff.amounts) { for (const { index, amount } of buff.amounts) {
if (card.skills[index]) card.skills[index].value = (card.skills[index].value || 0) + amount; if (card.skills[index]) card.skills[index].value = (card.skills[index].value || 0) + amount;

View File

@ -186,6 +186,10 @@ export class SkillProcessor {
t.currentArmor += skill.value; t.currentArmor += skill.value;
t._tempBuffs = t._tempBuffs || []; t._tempBuffs = t._tempBuffs || [];
t._tempBuffs.push({ stat: 'currentArmor', amount: skill.value, source: 'protect' }); t._tempBuffs.push({ stat: 'currentArmor', amount: skill.value, source: 'protect' });
if (t._moltZeroed) {
t.baseArmor = (t.baseArmor ?? 0) + skill.value;
t._tempBuffs.push({ stat: 'baseArmor', amount: skill.value, source: 'protect' });
}
protectTargets.push({ card: t, amount: skill.value, laneOffset }); protectTargets.push({ card: t, amount: skill.value, laneOffset });
} }
if (context) context.protectTargets = protectTargets; if (context) context.protectTargets = protectTargets;
@ -657,6 +661,10 @@ export class SkillProcessor {
t.currentArmor += skill.value; t.currentArmor += skill.value;
t._tempBuffs = t._tempBuffs || []; t._tempBuffs = t._tempBuffs || [];
t._tempBuffs.push({ stat: 'currentArmor', amount: skill.value, source: 'fortify' }); t._tempBuffs.push({ stat: 'currentArmor', amount: skill.value, source: 'fortify' });
if (t._moltZeroed) {
t.baseArmor = (t.baseArmor ?? 0) + skill.value;
t._tempBuffs.push({ stat: 'baseArmor', amount: skill.value, source: 'fortify' });
}
allTargets.push({ target: t, amount: skill.value }); allTargets.push({ target: t, amount: skill.value });
} }
if (context && allTargets.length > 0) context.fortifyAllTargets = allTargets; if (context && allTargets.length > 0) context.fortifyAllTargets = allTargets;
@ -665,6 +673,10 @@ export class SkillProcessor {
attacker.currentArmor += skill.value; attacker.currentArmor += skill.value;
attacker._tempBuffs = attacker._tempBuffs || []; attacker._tempBuffs = attacker._tempBuffs || [];
attacker._tempBuffs.push({ stat: 'currentArmor', amount: skill.value, source: 'fortify' }); attacker._tempBuffs.push({ stat: 'currentArmor', amount: skill.value, source: 'fortify' });
if (attacker._moltZeroed) {
attacker.baseArmor = (attacker.baseArmor ?? 0) + skill.value;
attacker._tempBuffs.push({ stat: 'baseArmor', amount: skill.value, source: 'fortify' });
}
if (context) { if (context) {
context.fortifyTarget = attacker; context.fortifyTarget = attacker;
context.fortifyAmount = skill.value; context.fortifyAmount = skill.value;

View File

@ -55,7 +55,7 @@ export class CampaignScene extends Phaser.Scene {
preload() { preload() {
this.load.video('corridors', 'assets/video/corridors.mp4'); this.load.video('corridors', 'assets/video/corridors.mp4');
// Load mission briefing videos // Load mission briefing videos
for (let i = 1; i <= 5; i++) { for (let i = 1; i <= 23; i++) {
const id = `mission_${String(i).padStart(3, '0')}`; const id = `mission_${String(i).padStart(3, '0')}`;
this.load.video(id, `assets/video/${id}.mp4`); this.load.video(id, `assets/video/${id}.mp4`);
} }