feat(deckbuilder): add unsaved changes warning and rebalance vampire cards
- **DeckBuilderScene**: - Introduced `hasUnsavedChanges` state to track modifications. - Added visual indicators: an "Unsaved Changes" badge in the deck panel and dynamic styling for the Save button. - Implemented a confirmation modal when attempting to leave the scene with unsaved changes. - Fixed scroll position restoration after re-rendering the card grid. - **Data (cards.json)**: - Rebalanced Vampire cards by swapping `drain` and `rupture` values (reduced drain, increased rupture). - Increased `rupture` values across higher-tier vampire cards (3, 6, 10, 15) to enhance their late-game scaling.
This commit is contained in:
parent
52808806a8
commit
d17379ca57
|
|
@ -5054,12 +5054,12 @@
|
|||
"skills": [
|
||||
{
|
||||
"name": "drain",
|
||||
"value": 2,
|
||||
"value": 1,
|
||||
"trigger": "preBattle"
|
||||
},
|
||||
{
|
||||
"name": "rupture",
|
||||
"value": 1,
|
||||
"value": 2,
|
||||
"trigger": "on_attack"
|
||||
}
|
||||
]
|
||||
|
|
@ -5077,7 +5077,7 @@
|
|||
},
|
||||
{
|
||||
"name": "rupture",
|
||||
"value": 2,
|
||||
"value": 3,
|
||||
"trigger": "on_attack"
|
||||
}
|
||||
]
|
||||
|
|
@ -5095,7 +5095,7 @@
|
|||
},
|
||||
{
|
||||
"name": "rupture",
|
||||
"value": 3,
|
||||
"value": 6,
|
||||
"trigger": "on_attack"
|
||||
}
|
||||
]
|
||||
|
|
@ -5113,7 +5113,7 @@
|
|||
},
|
||||
{
|
||||
"name": "rupture",
|
||||
"value": 4,
|
||||
"value": 10,
|
||||
"trigger": "on_attack"
|
||||
}
|
||||
]
|
||||
|
|
@ -5131,7 +5131,7 @@
|
|||
},
|
||||
{
|
||||
"name": "rupture",
|
||||
"value": 5,
|
||||
"value": 15,
|
||||
"trigger": "on_attack"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
this.scrollMask = null;
|
||||
this.scrollY = 0;
|
||||
this.maxScrollY = 0;
|
||||
this.hasUnsavedChanges = false;
|
||||
|
||||
// Grid layout constants (shared)
|
||||
this.CARD_W = 155;
|
||||
|
|
@ -111,7 +112,6 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
|
||||
// Create a container to hold all grid content
|
||||
this.scrollContainer = this.add.container(0, 0);
|
||||
this.scrollY = 0;
|
||||
|
||||
let cursorY = this.GRID_TOP;
|
||||
|
||||
|
|
@ -171,6 +171,10 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
const visibleH = this.SCROLL_BOTTOM - this.SCROLL_TOP;
|
||||
this.maxScrollY = Math.max(0, totalContentH - visibleH);
|
||||
|
||||
// Clamp and restore scroll position
|
||||
this.scrollY = Phaser.Math.Clamp(this.scrollY, 0, this.maxScrollY);
|
||||
this.scrollContainer.y = -this.scrollY;
|
||||
|
||||
// Apply rectangular mask so content doesn't overflow into header or footer
|
||||
const maskShape = this.make.graphics({ x: 0, y: 0 });
|
||||
maskShape.fillStyle(0xffffff);
|
||||
|
|
@ -291,6 +295,7 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
this.workingDeck.cards.push(card.id);
|
||||
}
|
||||
this.usedInDeck[card.id] = (this.usedInDeck[card.id] || 0) + 1;
|
||||
this.hasUnsavedChanges = true;
|
||||
this._renderCardGrid();
|
||||
this._renderDeckPanel();
|
||||
});
|
||||
|
|
@ -327,6 +332,13 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
}).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(title);
|
||||
|
||||
if (this.hasUnsavedChanges) {
|
||||
const unsavedBadge = this.add.text(panelCX, 112, '● UNSAVED CHANGES', {
|
||||
fontSize: '13px', color: '#ffaa00', fontFamily: 'Audiowide'
|
||||
}).setOrigin(0.5);
|
||||
this.deckPanelGroup.push(unsavedBadge);
|
||||
}
|
||||
|
||||
// ── Commander slot ──
|
||||
const cmdCard = this.workingDeck.commander ? this.cardManager.getCard(this.workingDeck.commander) : null;
|
||||
const cmdBg = this.add.rectangle(panelCX, 140, PANEL_W - 40, 46, cmdCard ? 0x1a3a5c : 0x1a1a2e)
|
||||
|
|
@ -346,6 +358,7 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
const oldCmd = this.workingDeck.commander;
|
||||
this.workingDeck.commander = null;
|
||||
if (oldCmd) delete this.usedInDeck[oldCmd];
|
||||
this.hasUnsavedChanges = true;
|
||||
this._renderDeckPanel();
|
||||
this._renderCardGrid();
|
||||
});
|
||||
|
|
@ -390,6 +403,7 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
this.workingDeck.cards.splice(idx, 1);
|
||||
this.usedInDeck[cardId] = (this.usedInDeck[cardId] || 1) - 1;
|
||||
if (this.usedInDeck[cardId] <= 0) delete this.usedInDeck[cardId];
|
||||
this.hasUnsavedChanges = true;
|
||||
this._renderDeckPanel();
|
||||
this._renderCardGrid();
|
||||
}
|
||||
|
|
@ -430,10 +444,12 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
|
||||
// ── Save / Clear buttons (recreated here to stay above the panel background) ──
|
||||
const saveBtnY = this.scale.height - 60;
|
||||
const saveBg = this.add.rectangle(panelCX - 110, saveBtnY, 190, 46, 0x224422)
|
||||
.setInteractive({ useHandCursor: true }).setStrokeStyle(2, 0x44aa44);
|
||||
const saveFill = this.hasUnsavedChanges ? 0x664400 : 0x224422;
|
||||
const saveBorder = this.hasUnsavedChanges ? 0xffaa00 : 0x44aa44;
|
||||
const saveBg = this.add.rectangle(panelCX - 110, saveBtnY, 190, 46, saveFill)
|
||||
.setInteractive({ useHandCursor: true }).setStrokeStyle(2, saveBorder);
|
||||
const saveTxt = this.add.text(panelCX - 110, saveBtnY, 'Save Deck', {
|
||||
fontSize: '18px', color: '#ffffff', fontFamily: 'Audiowide'
|
||||
fontSize: '18px', color: this.hasUnsavedChanges ? '#ffaa00' : '#ffffff', fontFamily: 'Audiowide'
|
||||
}).setOrigin(0.5);
|
||||
saveBg.on('pointerdown', () => {
|
||||
const save = this.registry.get('save');
|
||||
|
|
@ -441,7 +457,9 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
if (!validation.valid) { this._showMsg(validation.errors[0], '#ff4444'); return; }
|
||||
SaveManager.saveDeck(save, this.workingDeck);
|
||||
this.registry.set('save', save);
|
||||
this.hasUnsavedChanges = false;
|
||||
this._showMsg('Deck saved!', '#44ff44');
|
||||
this._renderDeckPanel();
|
||||
});
|
||||
|
||||
const clearBg = this.add.rectangle(panelCX + 110, saveBtnY, 190, 46, 0x442222)
|
||||
|
|
@ -453,6 +471,7 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
this.workingDeck.commander = null;
|
||||
this.workingDeck.cards = [];
|
||||
this.usedInDeck = {};
|
||||
this.hasUnsavedChanges = true;
|
||||
this._renderDeckPanel();
|
||||
this._renderCardGrid();
|
||||
});
|
||||
|
|
@ -562,7 +581,43 @@ export class DeckBuilderScene extends Phaser.Scene {
|
|||
this.add.text(80, this.scale.height - 40, 'Back', {
|
||||
fontSize: '18px', color: '#ffffff', fontFamily: 'Audiowide'
|
||||
}).setOrigin(0.5);
|
||||
backBtn.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
backBtn.on('pointerdown', () => {
|
||||
if (this.hasUnsavedChanges) {
|
||||
this._confirmLeave();
|
||||
} else {
|
||||
this.scene.start('MainMenuScene');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_confirmLeave() {
|
||||
const { width, height } = this.scale;
|
||||
const cx = width / 2;
|
||||
const cy = height / 2;
|
||||
|
||||
const overlay = this.add.rectangle(cx, cy, width, height, 0x000000, 0.7).setDepth(50);
|
||||
const box = this.add.rectangle(cx, cy, 420, 160, 0x0a0f1a).setStrokeStyle(2, 0xffaa00).setDepth(51);
|
||||
const msg = this.add.text(cx, cy - 36, 'You have unsaved changes.\nLeave without saving?', {
|
||||
fontSize: '17px', color: '#ffffff', fontFamily: 'Audiowide', align: 'center'
|
||||
}).setOrigin(0.5).setDepth(52);
|
||||
|
||||
const leaveBg = this.add.rectangle(cx - 90, cy + 36, 150, 40, 0x442222)
|
||||
.setInteractive({ useHandCursor: true }).setStrokeStyle(2, 0xaa4444).setDepth(51);
|
||||
const leaveTxt = this.add.text(cx - 90, cy + 36, 'Leave', {
|
||||
fontSize: '16px', color: '#ff6666', fontFamily: 'Audiowide'
|
||||
}).setOrigin(0.5).setDepth(52);
|
||||
|
||||
const stayBg = this.add.rectangle(cx + 90, cy + 36, 150, 40, 0x224422)
|
||||
.setInteractive({ useHandCursor: true }).setStrokeStyle(2, 0x44aa44).setDepth(51);
|
||||
const stayTxt = this.add.text(cx + 90, cy + 36, 'Stay', {
|
||||
fontSize: '16px', color: '#66ff66', fontFamily: 'Audiowide'
|
||||
}).setOrigin(0.5).setDepth(52);
|
||||
|
||||
const modal = [overlay, box, msg, leaveBg, leaveTxt, stayBg, stayTxt];
|
||||
const close = () => modal.forEach(o => { if (o.scene) o.destroy(); });
|
||||
|
||||
leaveBg.on('pointerdown', () => this.scene.start('MainMenuScene'));
|
||||
stayBg.on('pointerdown', close);
|
||||
}
|
||||
|
||||
_showMsg(msg, color) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue