211 lines
6.8 KiB
JavaScript
211 lines
6.8 KiB
JavaScript
const CATEGORY_COLORS = {
|
|
offense: '#ff8877',
|
|
support: '#88ff88',
|
|
control: '#88ccff',
|
|
defense: '#aaaaff',
|
|
special: '#ffcc44'
|
|
};
|
|
|
|
const TRIGGER_LABELS = {
|
|
preAttack: 'Pre-Attack',
|
|
on_attack: 'On Attack',
|
|
on_defend: 'On Defend',
|
|
preBattle: 'Pre-Battle',
|
|
passive: 'Passive',
|
|
on_turn_start: 'Turn Start',
|
|
on_kill: 'On Kill'
|
|
};
|
|
|
|
export class CardTooltip extends Phaser.GameObjects.Container {
|
|
/**
|
|
* @param {Phaser.Scene} scene
|
|
* @param {object} cardData - full card data (same object CardObject receives)
|
|
*/
|
|
constructor(scene, cardData) {
|
|
super(scene, 0, 0);
|
|
this.cardData = cardData;
|
|
this.setDepth(100);
|
|
this.setAlpha(0);
|
|
this._build();
|
|
scene.add.existing(this);
|
|
}
|
|
|
|
_build() {
|
|
const W = 420;
|
|
const PAD = 22;
|
|
const skillDefs = this.scene.cache.json.get('skills') || [];
|
|
const skillMap = {};
|
|
for (const sd of skillDefs) skillMap[sd.name] = sd;
|
|
|
|
// ── Collect text lines and their styles to measure total height ──────────
|
|
const lines = []; // { text, style, marginTop }
|
|
|
|
// Card name
|
|
lines.push({
|
|
text: this.cardData.name,
|
|
style: { fontSize: '28px', color: '#ffffff', fontStyle: 'bold', wordWrap: { width: W - PAD * 2 }, align: 'center', fontFamily: 'Audiowide' },
|
|
marginTop: 0, align: 'center'
|
|
});
|
|
|
|
// Type / Faction / Rarity
|
|
const meta = `${this._cap(this.cardData.type)} · ${this._cap(this.cardData.faction)} · ${this._cap(this.cardData.rarity)}`;
|
|
lines.push({
|
|
text: meta,
|
|
style: { fontSize: '16px', color: '#999999', wordWrap: { width: W - PAD * 2 }, align: 'center', fontFamily: 'Audiowide' },
|
|
marginTop: 6, align: 'center'
|
|
});
|
|
|
|
// Divider
|
|
lines.push({ divider: true, marginTop: 14 });
|
|
|
|
// Stats line
|
|
const stats = `ATK ${this.cardData.attack} HP ${this.cardData.health} ARM ${this.cardData.armor} DLY ${this.cardData.delay}`;
|
|
lines.push({
|
|
text: stats,
|
|
style: { fontSize: '20px', color: '#cccccc', fontStyle: 'bold', wordWrap: { width: W - PAD * 2 }, align: 'center', fontFamily: 'Audiowide' },
|
|
marginTop: 12, align: 'center'
|
|
});
|
|
|
|
// Divider before skills
|
|
if (this.cardData.skills?.length) {
|
|
lines.push({ divider: true, marginTop: 14 });
|
|
|
|
for (const skill of this.cardData.skills) {
|
|
const def = skillMap[skill.name];
|
|
const catColor = CATEGORY_COLORS[def?.category] || '#ffcc44';
|
|
const triggerLabel = TRIGGER_LABELS[skill.trigger] || skill.trigger;
|
|
|
|
// Skill header: "strike all 3 (Pre-Attack)"
|
|
const allMod = skill.all ? ' all' : '';
|
|
const valStr = skill.value != null ? ` ${skill.value}` : '';
|
|
lines.push({
|
|
text: `${skill.name}${allMod}${valStr}`,
|
|
style: { fontSize: '21px', color: catColor, fontStyle: 'bold', wordWrap: { width: W - PAD * 2 }, fontFamily: 'Audiowide' },
|
|
marginTop: 14, align: 'left',
|
|
suffix: { text: ` ${triggerLabel}`, style: { fontSize: '15px', color: '#888888', fontFamily: 'Audiowide' } }
|
|
});
|
|
|
|
// Skill description — use allDescription when "all" modifier is present
|
|
const rawDesc = (skill.all && def?.allDescription) ? def.allDescription : def?.description;
|
|
if (rawDesc) {
|
|
// Replace "value" with the actual number for clarity
|
|
const desc = skill.value != null
|
|
? rawDesc.replace(/\bvalue\b/g, `${skill.value}`)
|
|
: rawDesc;
|
|
lines.push({
|
|
text: desc,
|
|
style: { fontSize: '17px', color: '#bbbbbb', wordWrap: { width: W - PAD * 2 }, fontFamily: 'Audiowide' },
|
|
marginTop: 4, align: 'left'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flavor text
|
|
if (this.cardData.flavorText) {
|
|
lines.push({ divider: true, marginTop: 14 });
|
|
lines.push({
|
|
text: `"${this.cardData.flavorText}"`,
|
|
style: { fontSize: '16px', color: '#777777', fontStyle: 'italic', wordWrap: { width: W - PAD * 2 }, align: 'center', fontFamily: 'Audiowide' },
|
|
marginTop: 10, align: 'center'
|
|
});
|
|
}
|
|
|
|
// ── Measure total height by creating temporary text objects ──────────────
|
|
let cursorY = PAD;
|
|
const elements = []; // { type, y, ... }
|
|
|
|
for (const line of lines) {
|
|
cursorY += line.marginTop || 0;
|
|
if (line.divider) {
|
|
elements.push({ type: 'divider', y: cursorY });
|
|
cursorY += 1;
|
|
continue;
|
|
}
|
|
const tmp = this.scene.add.text(0, 0, line.text, line.style);
|
|
const h = tmp.height;
|
|
elements.push({ type: 'text', y: cursorY, height: h, line });
|
|
if (line.suffix) {
|
|
// suffix is rendered inline; doesn't add extra height
|
|
}
|
|
cursorY += h;
|
|
tmp.destroy();
|
|
}
|
|
cursorY += PAD;
|
|
const H = cursorY;
|
|
|
|
// ── Background panel ─────────────────────────────────────────────────────
|
|
const bg = this.scene.add.rectangle(W / 2, H / 2, W, H, 0x0c0c1e, 0.95)
|
|
.setStrokeStyle(2, 0x444466);
|
|
this.add(bg);
|
|
|
|
// ── Render text elements ─────────────────────────────────────────────────
|
|
for (const el of elements) {
|
|
if (el.type === 'divider') {
|
|
const div = this.scene.add.rectangle(W / 2, el.y, W - PAD * 2, 1, 0x333355);
|
|
this.add(div);
|
|
continue;
|
|
}
|
|
const { line, y } = el;
|
|
const x = line.align === 'center' ? W / 2 : PAD;
|
|
const origin = line.align === 'center' ? 0.5 : 0;
|
|
const txt = this.scene.add.text(x, y, line.text, line.style).setOrigin(origin, 0);
|
|
this.add(txt);
|
|
|
|
if (line.suffix) {
|
|
const sfx = this.scene.add.text(
|
|
txt.x + txt.width + 4, y + 5,
|
|
line.suffix.text, line.suffix.style
|
|
).setOrigin(0, 0);
|
|
this.add(sfx);
|
|
}
|
|
}
|
|
|
|
this._tooltipW = W;
|
|
this._tooltipH = H;
|
|
}
|
|
|
|
_cap(str) {
|
|
if (!str) return '';
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
/** Position the tooltip near the pointer, keeping it within the game bounds. */
|
|
positionAt(pointerX, pointerY) {
|
|
const { width, height } = this.scene.scale;
|
|
const OFFSET_X = 24;
|
|
const OFFSET_Y = 0;
|
|
|
|
let tx = pointerX + OFFSET_X;
|
|
let ty = pointerY + OFFSET_Y;
|
|
|
|
// Keep within right/left bounds
|
|
if (tx + this._tooltipW > width - 8) {
|
|
tx = pointerX - this._tooltipW - OFFSET_X;
|
|
}
|
|
if (tx < 8) tx = 8;
|
|
|
|
// Keep within bottom/top bounds
|
|
if (ty + this._tooltipH > height - 8) {
|
|
ty = height - 8 - this._tooltipH;
|
|
}
|
|
if (ty < 8) ty = 8;
|
|
|
|
this.setPosition(tx, ty);
|
|
}
|
|
|
|
show() {
|
|
this.setAlpha(1);
|
|
this.setVisible(true);
|
|
}
|
|
|
|
hide() {
|
|
this.setAlpha(0);
|
|
this.setVisible(false);
|
|
}
|
|
|
|
destroy() {
|
|
super.destroy();
|
|
}
|
|
}
|