feat: Update card skills, mission rosters, and enhance DeckBuilder UI

- **Data Updates**:
  - `cards.json`: Changed a skill from `rally` (preBattle) to `strike` (preAttack) with global effect.
  - `missions.json`: Swapped opponent cards in the raider mission roster (`raider_berserker_1` -> `raider_grunt_1`, `raider_reaver_1` -> `raider_scout_1`).

- **DeckBuilderScene.js Enhancements**:
  - **Visual Feedback**: Distinguishes between "unavailable" (greyed out) and "deck full" states. Cards remain visible when the deck is full, but the Add button turns red.
  - **Fusion Lab Integration**: Adds a pulsing "Fusion Available" overlay on assault cards where the player has 3+ spare copies, linking directly to the `FusionScene`.
  - **Button States**: Refined Add button logic to display specific messages for available, deck-full, and unavailable states with corresponding colors.
This commit is contained in:
Brian Fertig 2026-03-15 15:23:25 -06:00
parent 20e05116fe
commit d3f9facb4a
3 changed files with 81 additions and 12 deletions

View File

@ -1380,9 +1380,10 @@
"delay": 2, "delay": 2,
"skills": [ "skills": [
{ {
"name": "rally", "name": "strike",
"value": 3, "all": true,
"trigger": "preBattle" "value": 2,
"trigger": "preAttack"
} }
] ]
}, },

View File

@ -119,12 +119,12 @@
"opponent": { "opponent": {
"commander": "raider_cmd_1", "commander": "raider_cmd_1",
"cards": [ "cards": [
"raider_berserker_1", "raider_grunt_1",
"raider_berserker_1", "raider_berserker_1",
"raider_cutthroat_1", "raider_cutthroat_1",
"raider_warlord_1", "raider_warlord_1",
"raider_marauder_1", "raider_marauder_1",
"raider_reaver_1" "raider_scout_1"
] ]
}, },
"rewards": { "rewards": {

View File

@ -185,26 +185,94 @@ export class DeckBuilderScene extends Phaser.Scene {
const remaining = Math.max(0, owned - used); const remaining = Math.max(0, owned - used);
const canAdd = this._canAdd(card); const canAdd = this._canAdd(card);
// Deck-full block: assault card has available copies but the deck is at capacity.
// The card stays fully visible; only the button turns red.
const deckFull = card.type === 'assault' && this.workingDeck.cards.length >= 10;
const deckFullBlocked = deckFull && owned > 0 && remaining > 0;
// Card instance for display // Card instance for display
const inst = this.cardManager.createInstance(card.id); const inst = this.cardManager.createInstance(card.id);
const cardObj = new CardObject(this, cx, cy, inst, { width: this.CARD_W, height: this.CARD_H }); const cardObj = new CardObject(this, cx, cy, inst, { width: this.CARD_W, height: this.CARD_H });
if (!canAdd) cardObj.setAlpha(0.4); // Only dim cards that are genuinely unavailable (not owned / no copies left).
// When the deck is merely full, keep cards at full alpha so the player can browse.
if (!canAdd && !deckFullBlocked) cardObj.setAlpha(0.4);
this.scrollContainer.add(cardObj); this.scrollContainer.add(cardObj);
// ── Fusion available overlay ──────────────────────────────────────────────
// Show when the player has 3+ spare copies of a fusible card (assault, non-legendary).
if (card.type === 'assault' && card.rarity !== 'legendary' && remaining >= 3) {
// Sits over the art area (~upper-centre of the card)
const ovY = cy - Math.round(this.CARD_H * 0.18);
const ovW = this.CARD_W - 8;
const ovH = 46;
const ovBg = this.add.rectangle(cx, ovY, ovW, ovH, 0x0d0022, 0.92)
.setStrokeStyle(1, 0xaa44ff)
.setInteractive({ useHandCursor: true });
const ovIcon = this.add.text(cx, ovY - 9, '⚗ FUSION AVAILABLE', {
fontSize: '11px', color: '#cc88ff', fontStyle: 'bold', fontFamily: 'Audiowide'
}).setOrigin(0.5);
const ovSub = this.add.text(cx, ovY + 9, 'Click to open Fusion Lab →', {
fontSize: '10px', color: '#7744aa', fontFamily: 'Audiowide'
}).setOrigin(0.5);
// Hover tint
ovBg.on('pointerover', () => {
ovBg.setFillStyle(0x220044);
ovBg.setStrokeStyle(2, 0xdd88ff);
ovIcon.setColor('#ee99ff');
});
ovBg.on('pointerout', () => {
ovBg.setFillStyle(0x0d0022);
ovBg.setStrokeStyle(1, 0xaa44ff);
ovIcon.setColor('#cc88ff');
});
ovBg.on('pointerdown', () => this.scene.start('FusionScene'));
// Pulsing glow
this.tweens.add({
targets: [ovBg, ovIcon, ovSub],
alpha: { from: 0.75, to: 1 },
duration: 1100,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
this.scrollContainer.add([ovBg, ovIcon, ovSub]);
}
// Owned/used count // Owned/used count
const countY = cy + this.CARD_H / 2 + 4; const countY = cy + this.CARD_H / 2 + 4;
const countColor = canAdd ? '#88cc88' : deckFullBlocked ? '#aaaaaa' : '#555555';
const countText = this.add.text(cx, countY, `Owned: ${owned} | In Deck: ${used}`, { const countText = this.add.text(cx, countY, `Owned: ${owned} | In Deck: ${used}`, {
fontSize: '11px', color: canAdd ? '#88cc88' : '#555555', fontFamily: 'Audiowide' fontSize: '11px', color: countColor, fontFamily: 'Audiowide'
}).setOrigin(0.5, 0); }).setOrigin(0.5, 0);
this.scrollContainer.add(countText); this.scrollContainer.add(countText);
// +ADD button with remaining count // +ADD button — three visual states:
// green = can add (deck has room, copies available)
// red = copies available but deck is full
// grey = no copies available or not owned
const btnY = countY + 16; const btnY = countY + 16;
const btnLabel = canAdd ? `+ ADD (remain: ${remaining})` : '+ ADD'; let btnBgColor, btnStroke, btnTextColor, btnLabel;
const btnBg = this.add.rectangle(cx, btnY, this.CARD_W - 10, this.BTN_H, canAdd ? 0x225522 : 0x222222) if (canAdd) {
.setStrokeStyle(1, canAdd ? 0x44aa44 : 0x333333); btnBgColor = 0x225522; btnStroke = 0x44aa44; btnTextColor = '#44ff44';
btnLabel = `+ ADD (remain: ${remaining})`;
} else if (deckFullBlocked) {
btnBgColor = 0x552222; btnStroke = 0xaa4444; btnTextColor = '#ff4444';
btnLabel = `DECK FULL (${remaining} avail)`;
} else {
btnBgColor = 0x222222; btnStroke = 0x333333; btnTextColor = '#444444';
btnLabel = '+ ADD';
}
const btnBg = this.add.rectangle(cx, btnY, this.CARD_W - 10, this.BTN_H, btnBgColor)
.setStrokeStyle(1, btnStroke);
const btnTxt = this.add.text(cx, btnY, btnLabel, { const btnTxt = this.add.text(cx, btnY, btnLabel, {
fontSize: '12px', color: canAdd ? '#44ff44' : '#444444', fontFamily: 'Audiowide' fontSize: '12px', color: btnTextColor, fontFamily: 'Audiowide'
}).setOrigin(0.5); }).setOrigin(0.5);
this.scrollContainer.add(btnBg); this.scrollContainer.add(btnBg);
this.scrollContainer.add(btnTxt); this.scrollContainer.add(btnTxt);