Updates to research display

This commit is contained in:
Brian Fertig 2026-07-17 11:46:27 -06:00
parent 02b14b88c8
commit 8b5aa6064a
1 changed files with 54 additions and 16 deletions

View File

@ -1236,8 +1236,18 @@ export default class CivilizationGame extends Phaser.Scene {
const civ = this.state.civs[human];
if (!civ.alive) { this.onGameOver(); return; }
// Research prompt.
if (!civ.researching && Logic.availableTechs(this.rules, civ).length) {
// Research: announce the completed discovery FIRST, open the picker as
// that popup is dismissed (onDismiss replaces the queue-resume; the tech
// screen's close handler resumes it), and let everything else queue up
// behind. Without a completion event (fresh game, resumed save) the
// picker still opens directly.
const techEvent = this.state.events.find((e) => e.type === 'techDone' && e.civ === human && !e.announced);
const needsPick = !civ.researching && Logic.availableTechs(this.rules, civ).length > 0;
if (techEvent) {
techEvent.announced = true;
this.announceStatus(`Research complete: ${this.rules.techs[techEvent.tech].name}`,
needsPick ? () => this.openTech() : undefined);
} else if (needsPick) {
this.openTech();
}
this.presentAIProposals();
@ -1283,20 +1293,26 @@ export default class CivilizationGame extends Phaser.Scene {
// "resolve now, animate after" trick as the player's own moves, just
// via a before/after snapshot instead of a waypoint path.
const combatEvents = this.collectNewCombatEvents();
// Combats against any city the human has explored — their own or a
// rival's — are always deferred to a dedicated cinematic (camera pan +
// replay + outcome popup) played at the start of the human's next
// turn, rather than silently replayed here (possibly off-screen) like
// ordinary field battles. City tiles stay marked once explored (see
// MapView.refresh()'s `explored[idx]` check), so this reaches a known
// rival city even if it's currently outside the fog-of-war vision set.
// Combats against the HUMAN's cities are deferred to a dedicated
// cinematic (camera pan + replay + outcome popup) at the start of the
// human's next turn — those demand a response. Rival-vs-rival battles
// at cities the human has explored play live right here instead: pan,
// replay, and a status-log line, no blocking popup. City tiles stay
// marked once explored (see MapView.refresh()'s `explored[idx]`
// check), so this reaches a known rival city even if it's currently
// outside the fog-of-war vision set. `e.defenderCiv === human` catches
// a city the human just lost this step (city.civ already shows the
// conqueror by the time we classify).
const human = this.state.humanIndex;
const elsewhere = [];
const rivalCityCombats = [];
for (const e of combatEvents) {
const city = Logic.cityAt(this.state, e.x, e.y);
const cityExplored = city && this.state.explored[human][Logic.tileIndex(this.state.world, city.x, city.y)];
if (city && (city.civ === human || cityExplored)) {
if (city && (city.civ === human || e.defenderCiv === human)) {
this.pendingCityAttacks.push({ e, cityId: city.id, cityName: city.name, x: city.x, y: city.y });
} else if (city && cityExplored) {
rivalCityCombats.push({ e, city });
} else {
elsewhere.push(e);
}
@ -1318,8 +1334,25 @@ export default class CivilizationGame extends Phaser.Scene {
this.time.delayedCall(90, stepCiv);
});
};
// Rival-vs-rival city battles first (pan + replay + log line, one at a
// time), then ordinary visible field combats, then the move glide.
const playRivalCityCombats = (done) => {
const item = rivalCityCombats.shift();
if (!item) { done(); return; }
const { e, city } = item;
const attackerName = this.state.civs[e.attackerCiv]?.name ?? 'An enemy';
const captured = e.attackerWon && city.civ === e.attackerCiv;
const outcome = captured ? `${city.name} has fallen to ${attackerName}!`
: e.attackerWon ? `${city.name}'s defenders were defeated!`
: `${city.name} held its ground!`;
this.logMessage(`${attackerName} attacked ${city.name}${outcome}`);
this.view.panToTile(city.x, city.y);
this.view.animateCombat([e], () => playRivalCityCombats(done));
};
playRivalCityCombats(() => {
if (visibleCombat.length) this.view.animateCombat(visibleCombat, afterCombat);
else afterCombat();
});
};
stepCiv();
}
@ -1342,10 +1375,10 @@ export default class CivilizationGame extends Phaser.Scene {
}
}
// Combats against any city the human has explored (their own or a
// rival's), deferred from runToHumanTurn()'s per-civ stepping (see
// stepCiv there) so they always get a camera pan + replay + outcome popup
// instead of possibly playing silently off-screen.
// Combats against the human's own cities, deferred from runToHumanTurn()'s
// per-civ stepping (see stepCiv there) so they always get a camera pan +
// replay + outcome popup at the start of the human's turn. Rival-vs-rival
// city battles play live during the AI phase instead.
announceCityAttacks() {
const attacks = this.pendingCityAttacks;
this.pendingCityAttacks = [];
@ -1504,6 +1537,11 @@ export default class CivilizationGame extends Phaser.Scene {
openTechScreen(this, this.rules, this.state, () => {
this.modalOpen = false;
this.refreshHud();
// Resume any statuses that queued while the tech screen was up — in
// particular the start-of-turn flow, where the research-complete
// popup's onDismiss opens this screen with the rest of the turn's
// announcements still waiting in the queue.
this.showNextStatus();
});
}