From a7c3ef20e476755d17a90ec4e6f80f0ecbe86e6f Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Thu, 6 Aug 2026 16:50:28 -0600 Subject: [PATCH] fix Vega game: reset fleet selection on show, offset fleet animations, add building queue UI - Reset fleet selection when showing a new fleet so it starts at full strength - Offset moving fleet sprites by (-26, -22) to prevent blocking star click targets - Track newly unlocked building IDs in research completion events - Add one-click bulk-queue buttons in turn report for newly researched buildings - "All Colonies" queues at every colony - "Colonies with positive BC" filters by net income (trade minus upkeep) --- src/games/mastervega/VegaSidePanel.js | 12 +++-- src/games/mastervega/VegaStarMap.js | 10 ++-- src/games/mastervega/VegaTurnReport.js | 10 ++-- src/games/mastervega/VegaTurnReportScreen.js | 49 ++++++++++++++++++++ 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/games/mastervega/VegaSidePanel.js b/src/games/mastervega/VegaSidePanel.js index c8cfad4..8a68c0f 100644 --- a/src/games/mastervega/VegaSidePanel.js +++ b/src/games/mastervega/VegaSidePanel.js @@ -135,6 +135,9 @@ export default class VegaSidePanel { showFleet(fleet) { this.mode = 'fleet'; this.fleet = fleet; + // A fresh selection always starts at full strength: any dial-down from a + // prior selection (this fleet or another) must not carry over here. + this.sel = []; this.syncSelection(); this.rebuild(); this.open(); @@ -507,9 +510,12 @@ export default class VegaSidePanel { /** * Rebuild the per-stack selection, keeping whatever the player had already - * dialled in where the stack still exists. Immobile hulls (star bases) are - * excluded outright rather than shown at zero: they defend the system they - * were built in and can never be part of a move order. + * dialled in where the stack still exists. This carry-over only matters for + * refresh() (same selection, engine state changed under us); showFleet() + * clears this.sel first so a fresh selection always starts at full + * strength. Immobile hulls (star bases) are excluded outright rather than + * shown at zero: they defend the system they were built in and can never + * be part of a move order. */ syncSelection() { const fleet = this.fleet; diff --git a/src/games/mastervega/VegaStarMap.js b/src/games/mastervega/VegaStarMap.js index b82009d..c58b95a 100644 --- a/src/games/mastervega/VegaStarMap.js +++ b/src/games/mastervega/VegaStarMap.js @@ -335,8 +335,10 @@ export default class VegaStarMap { if (!a || !b) continue; if (viewer && !own && !viewer.explored[fleet.toStar]) continue; const t = fleet.total > 0 ? Phaser.Math.Clamp(fleet.progress / fleet.total, 0, 1) : 0; - x = a.x + (b.x - a.x) * t; - y = a.y + (b.y - a.y) * t; + // Offset up-left of the lane so a fleet just leaving its origin star + // doesn't sit directly on top of it and block the star's own click target. + x = a.x + (b.x - a.x) * t - 26; + y = a.y + (b.y - a.y) * t - 22; } const colour = Phaser.Display.Color.HexStringToColor(emp.color).color; @@ -501,8 +503,8 @@ export default class VegaStarMap { const from = this.state.galaxy.stars[f.fromStar]; if (!from) return null; const t = f.total > 0 ? Phaser.Math.Clamp(f.progress / f.total, 0, 1) : 0; - ax = from.x + (dest.x - from.x) * t; - ay = from.y + (dest.y - from.y) * t; + ax = from.x + (dest.x - from.x) * t - 26; + ay = from.y + (dest.y - from.y) * t - 22; } return { ax, ay, bx: dest.x, by: dest.y, diff --git a/src/games/mastervega/VegaTurnReport.js b/src/games/mastervega/VegaTurnReport.js index 875df2f..02e8e1b 100644 --- a/src/games/mastervega/VegaTurnReport.js +++ b/src/games/mastervega/VegaTurnReport.js @@ -103,16 +103,20 @@ function describeTechDone(rules, state, ev) { if (effects.length) { lines.push(line(`Effects: ${effects.map(([k, v]) => `${k} ${v > 0 ? '+' : ''}${v}`).join(', ')}`, '#7fd8a0')); } - const newBuildings = gate.buildings.map((id) => rules.buildings[id]?.name).filter(Boolean); + const newBuildingIds = gate.buildings.filter((id) => rules.buildings[id]); const newTechs = gate.prereqOf .filter((id) => emp.available[id] && !emp.known[id]) .map((id) => rules.techs[id]?.name) .filter(Boolean); - const unlocks = [...newBuildings, ...newTechs]; + const unlocks = [...newBuildingIds.map((id) => rules.buildings[id].name), ...newTechs]; if (unlocks.length) { lines.push(line(`Now available: ${unlocks.join(', ')}`, '#ffd88a')); } - return { headline: `Research completed: ${tech.name}.`, lines }; + return { + headline: `Research completed: ${tech.name}.`, + lines, + buildingUnlocks: newBuildingIds.map((id) => ({ id, name: rules.buildings[id].name })), + }; } function describeBuildingDone(rules, state, ev) { diff --git a/src/games/mastervega/VegaTurnReportScreen.js b/src/games/mastervega/VegaTurnReportScreen.js index b5afd7a..d9dee14 100644 --- a/src/games/mastervega/VegaTurnReportScreen.js +++ b/src/games/mastervega/VegaTurnReportScreen.js @@ -17,6 +17,16 @@ import { buildingFrame } from './VegaArt.js'; import { turnToYear } from './VegaRules.js'; import { Button } from '../../ui/Button.js'; import { describeEvent, categoryWeight } from './VegaTurnReport.js'; +import { enqueue, empireColonies, colonyTrade } from './VegaLogic.js'; + +// A colony's own trade minus what its existing buildings already cost to run +// — there's no per-colony BC in this economy (only the empire-wide pool), so +// this is the closest local stand-in for "can this colony carry one more +// building's upkeep on its own income." +function colonyNetIncome(rules, state, colony) { + const upkeep = colony.buildings.reduce((s, bid) => s + (rules.buildings[bid]?.upkeep ?? 0), 0); + return colonyTrade(rules, state, colony) - upkeep; +} const ACCENT = 0x6fc4ff; const ROW_BG = 0x142238; @@ -96,6 +106,45 @@ export function openTurnReportScreen(scene, rules, state, events, onClose) { } cy += 4; + // A newly-researched tech that unlocks a colony building gets a + // one-click bulk-queue offer right in the report — the whole reason + // to interrupt the player for research news instead of leaving it in + // the ticker log. + if (ev.type === 'techDone') { + for (const bu of (desc.buildingUnlocks ?? [])) { + const label = scene.add.text(16, cy, `Build ${bu.name}:`, { + fontFamily: FONT, fontSize: '15px', color: '#ffd88a', + }); + col.content.add(label); + cy += label.height + 8; + + const status = scene.add.text(16, cy + 34, '', { + fontFamily: FONT, fontSize: '13px', color: '#7fd8a0', wordWrap: { width: w - 32 }, + }); + + const queueAt = (colonies) => { + let n = 0; + for (const c of colonies) if (enqueue(rules, state, c, 'building', bu.id)) n += 1; + status.setText(n > 0 + ? `Queued at ${n} colon${n === 1 ? 'y' : 'ies'}.` + : 'Already built or queued at every colony.'); + }; + + const allBtn = new Button(scene, 16 + 84, cy + 14, 'All Colonies', () => { + queueAt(empireColonies(state, ev.empire)); + }, { width: 168, height: 28, fontSize: 13 }); + col.content.add(allBtn); + + const bcBtn = new Button(scene, 16 + 168 + 16 + 105, cy + 14, 'Colonies with positive BC', () => { + queueAt(empireColonies(state, ev.empire).filter((c) => colonyNetIncome(rules, state, c) > 0)); + }, { width: 210, height: 28, fontSize: 13 }); + col.content.add(bcBtn); + + col.content.add(status); + cy += 28 + 14 + status.height + 10; + } + } + // The built item's picture, right-aligned against the synopsis text // it sits beside. Reserving mediaW off the wrap width above (rather // than overlaying it) keeps long descriptions from running under it.