feat: Implement Siege mechanic and add Fullscreen toggle
- **Combat Logic**: Changed `siege` skill trigger from `on_attack` to `preBattle`. - **Engine Updates**: Modified `_collectPreBattleBuffs` (renamed to `_collectPreBattleFires`) in `CombatEngine.js` to capture and log siege damage events separately from buffs. - **Skill Processor**: Updated `SkillProcessor.js` to record `siegeTarget` and `siegeDamage` in the context for event logging. - **Data Updates**: Adjusted descriptions and triggers in `cards.json` and `skills.json`. - **UI**: Added a "Toggle Fullscreen" button to the Main Menu with state-aware labeling.
This commit is contained in:
parent
315b3ddccd
commit
031264bbb9
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
|
@ -348,7 +348,7 @@
|
||||||
{
|
{
|
||||||
"name": "siege",
|
"name": "siege",
|
||||||
"value": 2,
|
"value": 2,
|
||||||
"trigger": "on_attack"
|
"trigger": "preBattle"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"flavorText": "Leave nothing standing. Take everything else.",
|
"flavorText": "Leave nothing standing. Take everything else.",
|
||||||
|
|
@ -413,7 +413,7 @@
|
||||||
{
|
{
|
||||||
"name": "siege",
|
"name": "siege",
|
||||||
"value": 5,
|
"value": 5,
|
||||||
"trigger": "on_attack"
|
"trigger": "preBattle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "strike",
|
"name": "strike",
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "siege",
|
"name": "siege",
|
||||||
"description": "Deal value damage directly to the enemy commander, ignoring lane cards.",
|
"description": "Before battle: launch siege missiles directly at the enemy commander for value damage (armor applies).",
|
||||||
"trigger": "on_attack",
|
"trigger": "preBattle",
|
||||||
"category": "offense"
|
"category": "offense"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -337,24 +337,29 @@ export class CombatEngine {
|
||||||
return [...this.events];
|
return [...this.events];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process preBattle skills for a set of cards and return buff descriptors.
|
// Process preBattle skills for a set of cards and return buff + siege fire descriptors.
|
||||||
_collectPreBattleBuffs(cards, allies, enemies) {
|
_collectPreBattleFires(cards, allies, enemies, enemyCommander) {
|
||||||
const buffs = [];
|
const buffs = [];
|
||||||
|
const siegeFires = [];
|
||||||
const liveAllies = allies.filter(c => c.currentHP > 0);
|
const liveAllies = allies.filter(c => c.currentHP > 0);
|
||||||
const liveEnemies = enemies.filter(c => c.currentHP > 0);
|
const liveEnemies = enemies.filter(c => c.currentHP > 0);
|
||||||
for (const card of cards) {
|
for (const card of cards) {
|
||||||
if (card.currentHP <= 0) continue;
|
if (card.currentHP <= 0) continue;
|
||||||
for (const s of card.skills) {
|
for (const s of card.skills) {
|
||||||
if (s.trigger === 'preBattle') {
|
if (s.trigger !== 'preBattle') continue;
|
||||||
const ctx = { rng: this.rng };
|
const ctx = { rng: this.rng, enemyCommander };
|
||||||
this.skillProcessor.process(s, card, null, liveAllies, liveEnemies, ctx);
|
this.skillProcessor.process(s, card, null, liveAllies, liveEnemies, ctx);
|
||||||
if (ctx.rallyTarget) {
|
if (s.name === 'rally' && ctx.rallyTarget) {
|
||||||
buffs.push({ skill: 'rally', source: card, target: ctx.rallyTarget, amount: s.value });
|
buffs.push({ skill: 'rally', source: card, target: ctx.rallyTarget, amount: s.value });
|
||||||
}
|
}
|
||||||
|
if (s.name === 'siege' && ctx.siegeTarget) {
|
||||||
|
const hpBefore = ctx.siegeTarget.currentHP + ctx.siegeDamage;
|
||||||
|
siegeFires.push({ skill: 'siege', source: card, target: ctx.siegeTarget, damage: ctx.siegeDamage, hpBefore });
|
||||||
|
this._log(`${card.name} siege hits ${ctx.siegeTarget.name} for ${ctx.siegeDamage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buffs;
|
return { buffs, siegeFires };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit the 8-step pre-battle sequence and process preBattle skills.
|
// Emit the 8-step pre-battle sequence and process preBattle skills.
|
||||||
|
|
@ -369,24 +374,24 @@ export class CombatEngine {
|
||||||
const otherAllies = [otherCmd, ...otherLanes];
|
const otherAllies = [otherCmd, ...otherLanes];
|
||||||
|
|
||||||
// Steps 1–2: commander defensive buffs (placeholder — no defensive skills yet)
|
// Steps 1–2: commander defensive buffs (placeholder — no defensive skills yet)
|
||||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: firstSide, card: firstCmd, buffs: [] });
|
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: firstSide, card: firstCmd, buffs: [], siegeFires: [] });
|
||||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: otherSide, card: otherCmd, buffs: [] });
|
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: otherSide, card: otherCmd, buffs: [], siegeFires: [] });
|
||||||
|
|
||||||
// Steps 3–4: commander offensive buffs
|
// Steps 3–4: commander offensive buffs
|
||||||
const firstCmdBuffs = this._collectPreBattleBuffs([firstCmd], firstAllies, otherAllies);
|
const firstCmdFires = this._collectPreBattleFires([firstCmd], firstAllies, otherAllies, otherCmd);
|
||||||
const otherCmdBuffs = this._collectPreBattleBuffs([otherCmd], otherAllies, firstAllies);
|
const otherCmdFires = this._collectPreBattleFires([otherCmd], otherAllies, firstAllies, firstCmd);
|
||||||
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: firstSide, card: firstCmd, buffs: firstCmdBuffs });
|
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: firstSide, card: firstCmd, buffs: firstCmdFires.buffs, siegeFires: firstCmdFires.siegeFires });
|
||||||
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: otherSide, card: otherCmd, buffs: otherCmdBuffs });
|
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: otherSide, card: otherCmd, buffs: otherCmdFires.buffs, siegeFires: otherCmdFires.siegeFires });
|
||||||
|
|
||||||
// Steps 5–6: lane defensive buffs (placeholder)
|
// Steps 5–6: lane defensive buffs (placeholder)
|
||||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: firstSide, cards: firstLanes, buffs: [] });
|
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: firstSide, cards: firstLanes, buffs: [], siegeFires: [] });
|
||||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: otherSide, cards: otherLanes, buffs: [] });
|
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: otherSide, cards: otherLanes, buffs: [], siegeFires: [] });
|
||||||
|
|
||||||
// Steps 7–8: lane offensive buffs
|
// Steps 7–8: lane offensive buffs + siege fires
|
||||||
const firstLaneBuffs = this._collectPreBattleBuffs(firstLanes, firstAllies, otherAllies);
|
const firstLaneFires = this._collectPreBattleFires(firstLanes, firstAllies, otherAllies, otherCmd);
|
||||||
const otherLaneBuffs = this._collectPreBattleBuffs(otherLanes, otherAllies, firstAllies);
|
const otherLaneFires = this._collectPreBattleFires(otherLanes, otherAllies, firstAllies, firstCmd);
|
||||||
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'lanes', side: firstSide, cards: firstLanes, buffs: firstLaneBuffs });
|
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'lanes', side: firstSide, cards: firstLanes, buffs: firstLaneFires.buffs, siegeFires: firstLaneFires.siegeFires });
|
||||||
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'lanes', side: otherSide, cards: otherLanes, buffs: otherLaneBuffs });
|
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'lanes', side: otherSide, cards: otherLanes, buffs: otherLaneFires.buffs, siegeFires: otherLaneFires.siegeFires });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the ordered list of attacks for this turn (called by beginCommit).
|
// Build the ordered list of attacks for this turn (called by beginCommit).
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ export class SkillProcessor {
|
||||||
if (context.enemyCommander) {
|
if (context.enemyCommander) {
|
||||||
const dmg = Math.max(0, skill.value - Math.max(0, context.enemyCommander.currentArmor));
|
const dmg = Math.max(0, skill.value - Math.max(0, context.enemyCommander.currentArmor));
|
||||||
context.enemyCommander.currentHP -= dmg;
|
context.enemyCommander.currentHP -= dmg;
|
||||||
|
context.siegeTarget = context.enemyCommander;
|
||||||
|
context.siegeDamage = dmg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ export class MainMenuScene extends Phaser.Scene {
|
||||||
this.scene.start(btn.scene, btn.data || {});
|
this.scene.start(btn.scene, btn.data || {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fullscreen toggle — utility button, visually distinct
|
||||||
|
const fsY = startY + buttons.length * spacing + 20;
|
||||||
|
this._makeFullscreenButton(width / 2, fsY);
|
||||||
}
|
}
|
||||||
|
|
||||||
_makeButton(x, y, label, callback) {
|
_makeButton(x, y, label, callback) {
|
||||||
|
|
@ -67,4 +71,28 @@ export class MainMenuScene extends Phaser.Scene {
|
||||||
|
|
||||||
return { bg, text };
|
return { bg, text };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_makeFullscreenButton(x, y) {
|
||||||
|
const getLabel = () => this.scale.isFullscreen ? '⛶ Exit Fullscreen' : '⛶ Toggle Fullscreen';
|
||||||
|
|
||||||
|
const bg = this.add.rectangle(x, y, 320, 50, 0x1a2a1a)
|
||||||
|
.setInteractive({ useHandCursor: true })
|
||||||
|
.setStrokeStyle(2, 0x448844);
|
||||||
|
|
||||||
|
const text = this.add.text(x, y, getLabel(), {
|
||||||
|
fontSize: '20px', color: '#88cc88'
|
||||||
|
}).setOrigin(0.5);
|
||||||
|
|
||||||
|
bg.on('pointerover', () => bg.setFillStyle(0x2a3f2a));
|
||||||
|
bg.on('pointerout', () => bg.setFillStyle(0x1a2a1a));
|
||||||
|
bg.on('pointerdown', () => {
|
||||||
|
if (this.scale.isFullscreen) {
|
||||||
|
this.scale.stopFullscreen();
|
||||||
|
} else {
|
||||||
|
this.scale.startFullscreen();
|
||||||
|
}
|
||||||
|
// Update label after a short delay to let the state change
|
||||||
|
this.time.delayedCall(100, () => text.setText(getLabel()));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue