feat(mastervega): refine star map colony info, tech effects display, and turn report layout
- Add colony info layer showing per-empire colony rosters (name, orbit, pop) stacked up-left of colonized stars with colored bars and connector lines - Change "Range as light" to "Explored as light": reveal only actually explored stars (not fuel-reachable) with smaller 1.1pc radius - Extract VegaTechEffects.js with human-readable descriptions for all tech effects, replacing raw key/value dumps in turn report - Expand building unlock rows to include building descriptions - Reorganize ship completion rows: hull icon and commander portrait now display below the full-width synopsis instead of beside it - Bump zuma level 8 config to level 21 "Sun Cove" with new path and params
This commit is contained in:
parent
a7c3ef20e4
commit
0adb689c19
|
|
@ -1,165 +1,106 @@
|
|||
{
|
||||
"level": 8,
|
||||
"name": "Sun Court",
|
||||
"frog": [
|
||||
960,
|
||||
580
|
||||
],
|
||||
"colors": 5,
|
||||
"background": "zuma-background-2",
|
||||
"seed": 64352,
|
||||
"starScores": [
|
||||
1010,
|
||||
1520,
|
||||
2020
|
||||
],
|
||||
"paths": [
|
||||
{
|
||||
"shape": "horseshoe",
|
||||
"points": [
|
||||
[
|
||||
-80,
|
||||
130
|
||||
],
|
||||
[
|
||||
60,
|
||||
130
|
||||
],
|
||||
[
|
||||
960,
|
||||
130
|
||||
],
|
||||
[
|
||||
1191,
|
||||
154
|
||||
],
|
||||
[
|
||||
1398,
|
||||
210
|
||||
],
|
||||
[
|
||||
1565,
|
||||
294
|
||||
],
|
||||
[
|
||||
1680,
|
||||
399
|
||||
],
|
||||
[
|
||||
1734,
|
||||
514
|
||||
],
|
||||
[
|
||||
1727,
|
||||
632
|
||||
],
|
||||
[
|
||||
1659,
|
||||
742
|
||||
],
|
||||
[
|
||||
1538,
|
||||
836
|
||||
],
|
||||
[
|
||||
1375,
|
||||
908
|
||||
],
|
||||
[
|
||||
1185,
|
||||
951
|
||||
],
|
||||
[
|
||||
983,
|
||||
964
|
||||
],
|
||||
[
|
||||
785,
|
||||
946
|
||||
],
|
||||
[
|
||||
607,
|
||||
900
|
||||
],
|
||||
[
|
||||
462,
|
||||
831
|
||||
],
|
||||
[
|
||||
361,
|
||||
743
|
||||
],
|
||||
[
|
||||
311,
|
||||
646
|
||||
],
|
||||
[
|
||||
313,
|
||||
547
|
||||
],
|
||||
[
|
||||
366,
|
||||
454
|
||||
],
|
||||
[
|
||||
464,
|
||||
374
|
||||
],
|
||||
[
|
||||
598,
|
||||
313
|
||||
],
|
||||
[
|
||||
755,
|
||||
275
|
||||
],
|
||||
[
|
||||
923,
|
||||
263
|
||||
],
|
||||
[
|
||||
1087,
|
||||
276
|
||||
],
|
||||
[
|
||||
1235,
|
||||
312
|
||||
],
|
||||
[
|
||||
1356,
|
||||
368
|
||||
],
|
||||
[
|
||||
1441,
|
||||
438
|
||||
],
|
||||
[
|
||||
1485,
|
||||
517
|
||||
],
|
||||
[
|
||||
1485,
|
||||
598
|
||||
],
|
||||
[
|
||||
1445,
|
||||
673
|
||||
],
|
||||
[
|
||||
1368,
|
||||
739
|
||||
]
|
||||
],
|
||||
"tunnels": [
|
||||
[
|
||||
1857,
|
||||
2403
|
||||
]
|
||||
],
|
||||
"quota": 30,
|
||||
"introBalls": 9,
|
||||
"pushSpeed": 96,
|
||||
"powerUpRate": 0.055
|
||||
}
|
||||
]
|
||||
"level": 21,
|
||||
"name": "Sun Cove",
|
||||
"frog": [
|
||||
974,
|
||||
548
|
||||
],
|
||||
"colors": 4,
|
||||
"seed": 12345,
|
||||
"starScores": [
|
||||
640,
|
||||
960,
|
||||
1280
|
||||
],
|
||||
"paths": [
|
||||
{
|
||||
"shape": "custom",
|
||||
"points": [
|
||||
[
|
||||
-34,
|
||||
173
|
||||
],
|
||||
[
|
||||
609,
|
||||
138
|
||||
],
|
||||
[
|
||||
1474,
|
||||
143
|
||||
],
|
||||
[
|
||||
1755,
|
||||
292
|
||||
],
|
||||
[
|
||||
1786,
|
||||
592
|
||||
],
|
||||
[
|
||||
1390,
|
||||
811
|
||||
],
|
||||
[
|
||||
982,
|
||||
815
|
||||
],
|
||||
[
|
||||
501,
|
||||
585
|
||||
]
|
||||
],
|
||||
"tunnels": [],
|
||||
"quota": 24,
|
||||
"introBalls": 8,
|
||||
"pushSpeed": 30,
|
||||
"powerUpRate": 0.06
|
||||
},
|
||||
{
|
||||
"shape": "custom",
|
||||
"points": [
|
||||
[
|
||||
-143,
|
||||
948
|
||||
],
|
||||
[
|
||||
1072,
|
||||
963
|
||||
],
|
||||
[
|
||||
1634,
|
||||
911
|
||||
],
|
||||
[
|
||||
1595,
|
||||
397
|
||||
],
|
||||
[
|
||||
1343,
|
||||
241
|
||||
],
|
||||
[
|
||||
945,
|
||||
270
|
||||
],
|
||||
[
|
||||
455,
|
||||
403
|
||||
],
|
||||
[
|
||||
289,
|
||||
571
|
||||
]
|
||||
],
|
||||
"tunnels": [
|
||||
[
|
||||
1793,
|
||||
2411
|
||||
]
|
||||
],
|
||||
"quota": 24,
|
||||
"introBalls": 8,
|
||||
"pushSpeed": 30,
|
||||
"powerUpRate": 0.06
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,27 @@ import { Tooltip } from '../../ui/Tooltip.js';
|
|||
import { makeNebula } from './VegaNebula.js';
|
||||
import { PARSEC_PX, parsecs, mulberry32 } from './VegaGalaxyGen.js';
|
||||
import {
|
||||
reachableStars, coloniesAt, empireColonies, fleetEta, habitableForEmpire,
|
||||
coloniesAt, empireColonies, fleetEta, habitableForEmpire,
|
||||
} from './VegaLogic.js';
|
||||
import { starFrame } from './VegaArt.js';
|
||||
import { buildZoomLadder, DEFAULT_ZOOM_INDEX } from './VegaZoom.js';
|
||||
import { describeStarTooltip } from './VegaTooltips.js';
|
||||
import { ORBIT } from './VegaScreens.js';
|
||||
|
||||
const FONT = '"Julius Sans One"';
|
||||
|
||||
// Per-empire colony roster block drawn up-left of a colonized star. Sizing
|
||||
// is tuned to clear both the star's own ownership ring and the in-transit
|
||||
// fleet marker/comet trail, which already occupies the same upper-left
|
||||
// quadrant at (star.x - 26, star.y - 22).
|
||||
const COLONY_INFO_GAP_X = 112;
|
||||
const COLONY_INFO_GAP_Y = 36;
|
||||
const COLONY_INFO_BAR_H = 12;
|
||||
const COLONY_INFO_BAR_TEXT_GAP = 8;
|
||||
const COLONY_INFO_GROUP_GAP = 20;
|
||||
const COLONY_INFO_MIN_BAR_W = 80;
|
||||
const COLONY_INFO_TEXT_SIZE = 26;
|
||||
|
||||
// The range and territory fields are painted into low-resolution
|
||||
// RenderTextures and scaled up. A huge galaxy is 5400px wide — past the safe
|
||||
// single-texture size — and the upscale blur is exactly the soft edge both
|
||||
|
|
@ -101,6 +114,11 @@ export default class VegaStarMap {
|
|||
.setOrigin(0, 0).setScale(FIELD_DIV);
|
||||
this.root.add(this.rangeRT);
|
||||
|
||||
// Sits behind starLayer (insertion order) so every star sprite paints
|
||||
// over the connecting lines this layer draws to its own center.
|
||||
this.colonyInfoLayer = scene.add.container(0, 0);
|
||||
this.root.add(this.colonyInfoLayer);
|
||||
|
||||
this.starLayer = scene.add.container(0, 0);
|
||||
this.root.add(this.starLayer);
|
||||
// The selection reticle sits above the stars and below the fleet markers,
|
||||
|
|
@ -218,9 +236,11 @@ export default class VegaStarMap {
|
|||
|
||||
// ------------------------------------------------------------- the fields
|
||||
|
||||
// "Range as light": the galaxy is covered in darkness, and everything inside
|
||||
// fuel range is erased back out of it. Every propulsion tech literally lights
|
||||
// up more of the map, which is the clearest progression signal a 4X can give.
|
||||
// "Explored as light": the galaxy is covered in darkness, and each star the
|
||||
// player has actually explored (colonised, or had a fleet visit) gets a soft
|
||||
// patch erased around it. Deliberately NOT fuel range — reachability is left
|
||||
// for the player to work out by trial and error rather than telegraphed on
|
||||
// the map.
|
||||
// A scratch image reused for every stamp. RenderTexture.erase()/draw() honour
|
||||
// a game object's scale and tint, but NOT a bare texture key's — a key is
|
||||
// always stamped at its native 256px. Everything here needs a radius that
|
||||
|
|
@ -244,14 +264,14 @@ export default class VegaStarMap {
|
|||
|
||||
const emp = this.state.empires[this.viewerIdx];
|
||||
if (!emp) return;
|
||||
const reach = reachableStars(this.rules, this.state, this.viewerIdx);
|
||||
// One soft disc per reachable star, wide enough that neighbouring discs
|
||||
// overlap — otherwise the lit region reads as a string of beads instead of
|
||||
// one continuous sphere of influence.
|
||||
const radius = PARSEC_PX * 2.6;
|
||||
// One soft disc per explored star — sized to clear just that star's own
|
||||
// neighbourhood, not to bridge gaps into a "sphere of influence" the way
|
||||
// the old fuel-range version did (that would re-imply territory the
|
||||
// player hasn't actually scouted).
|
||||
const radius = PARSEC_PX * 1.1;
|
||||
const img = this.stamp((radius * 2) / 256 / FIELD_DIV);
|
||||
for (const key of Object.keys(reach)) {
|
||||
const star = this.state.galaxy.stars[Number(key)];
|
||||
for (const star of this.state.galaxy.stars) {
|
||||
if (!emp.explored[star.idx]) continue;
|
||||
rt.erase(img, star.x / FIELD_DIV, star.y / FIELD_DIV);
|
||||
}
|
||||
}
|
||||
|
|
@ -283,6 +303,7 @@ export default class VegaStarMap {
|
|||
this.redrawRange();
|
||||
this.redrawTerritory();
|
||||
this.refreshStars();
|
||||
this.refreshColonyInfo();
|
||||
this.refreshFleets();
|
||||
this.refreshLabels();
|
||||
}
|
||||
|
|
@ -312,6 +333,64 @@ export default class VegaStarMap {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-empire colony roster stacked up-left of a colonized star: a thick
|
||||
* owner-colored bar over a "name (pop. N)" list, one group per empire that
|
||||
* holds a colony at the star, each with its own line running to the star's
|
||||
* center. Only shown at the closest zoom tier — same density tier as the
|
||||
* "N worlds · M habitable" subtext — to keep the mid/far zoom map legible.
|
||||
*/
|
||||
refreshColonyInfo() {
|
||||
this.colonyInfoLayer.removeAll(true);
|
||||
if (this.isFarZoom || !this.isNearZoom) return;
|
||||
const { state } = this;
|
||||
const viewer = this.viewerIdx >= 0 ? state.empires[this.viewerIdx] : null;
|
||||
const textSize = Math.round(COLONY_INFO_TEXT_SIZE / Math.max(0.7, this.zoom));
|
||||
|
||||
for (const s of this.starSprites) {
|
||||
if (viewer && !viewer.explored[s.star.idx]) continue;
|
||||
const cols = coloniesAt(state, s.star.idx);
|
||||
if (!cols.length) continue;
|
||||
|
||||
const groups = [];
|
||||
for (const emp of state.empires) {
|
||||
const own = cols.filter((c) => c.empireIdx === emp.idx).sort((a, b) => a.orbit - b.orbit);
|
||||
if (!own.length) continue;
|
||||
const text = this.scene.add.text(0, 0, own.map((c) => (
|
||||
`${s.star.name} ${ORBIT[c.orbit] ?? c.orbit + 1} (pop. ${c.pop.toFixed(1)})`
|
||||
)).join('\n'), {
|
||||
fontFamily: FONT, fontSize: `${textSize}px`, color: '#f0f4fa', lineSpacing: 2,
|
||||
}).setOrigin(1, 0);
|
||||
groups.push({ emp, text });
|
||||
}
|
||||
if (!groups.length) continue;
|
||||
|
||||
const barWidth = Math.max(COLONY_INFO_MIN_BAR_W, ...groups.map((g) => g.text.width));
|
||||
const blockRightX = s.star.x - COLONY_INFO_GAP_X;
|
||||
const totalHeight = groups.reduce((h, g) => (
|
||||
h + COLONY_INFO_BAR_H + COLONY_INFO_BAR_TEXT_GAP + g.text.height
|
||||
), 0) + COLONY_INFO_GROUP_GAP * (groups.length - 1);
|
||||
let y = s.star.y - COLONY_INFO_GAP_Y - totalHeight;
|
||||
|
||||
const lines = this.scene.add.graphics();
|
||||
this.colonyInfoLayer.add(lines);
|
||||
for (const g of groups) {
|
||||
const colour = Phaser.Display.Color.HexStringToColor(g.emp.color).color;
|
||||
const bar = this.scene.add.rectangle(
|
||||
blockRightX - barWidth / 2, y + COLONY_INFO_BAR_H / 2, barWidth, COLONY_INFO_BAR_H, colour,
|
||||
).setStrokeStyle(2, 0x000000, 0.85);
|
||||
this.colonyInfoLayer.add(bar);
|
||||
lines.lineStyle(3, colour, 0.7);
|
||||
lines.lineBetween(blockRightX, y + COLONY_INFO_BAR_H / 2, s.star.x, s.star.y);
|
||||
|
||||
g.text.setPosition(blockRightX, y + COLONY_INFO_BAR_H + COLONY_INFO_BAR_TEXT_GAP);
|
||||
this.colonyInfoLayer.add(g.text);
|
||||
|
||||
y += COLONY_INFO_BAR_H + COLONY_INFO_BAR_TEXT_GAP + g.text.height + COLONY_INFO_GROUP_GAP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshFleets() {
|
||||
this.fleetLayer.removeAll(true);
|
||||
const { state, rules } = this;
|
||||
|
|
@ -664,6 +743,7 @@ export default class VegaStarMap {
|
|||
this.clampPan();
|
||||
this.refreshLabels();
|
||||
this.refreshStars();
|
||||
this.refreshColonyInfo();
|
||||
this.cb.onZoom?.(next);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
// Master of Vega — human-readable descriptions for `tech.effects` values.
|
||||
// Headless, no Phaser: kept separate from VegaTurnReport.js so any other
|
||||
// screen (a future tech-tree view, say) can reuse the same wording without
|
||||
// pulling in turn-report-specific code.
|
||||
//
|
||||
// Every formatter is hand-matched to how the effect is actually consumed
|
||||
// elsewhere in the engine (VegaCombat.js/VegaLogic.js/VegaShips.js) — not a
|
||||
// generic key/value dump. A few effects (cloaked, scanRange, singularity)
|
||||
// are currently inert stats with no downstream mechanic wired up yet; their
|
||||
// wording stays modest/flavor rather than promising a numeric benefit that
|
||||
// doesn't exist in the sim.
|
||||
|
||||
const pct = (mult) => `${Math.round(Math.abs(mult - 1) * 100)}%`;
|
||||
|
||||
function describeWeapon(w) {
|
||||
const perRound = w.shots > 1 ? `, ${w.shots} shots per round` : '';
|
||||
const pierce = w.shieldPierce
|
||||
? `, pierces ${w.shieldPierce} point${w.shieldPierce === 1 ? '' : 's'} of enemy shields` : '';
|
||||
const cracker = w.planetCracker ? ' — powerful enough to crack a planet apart' : '';
|
||||
return `New ${w.kind} weapon available: ${w.name}, ${w.min}-${w.max} damage per shot${perRound}${pierce}${cracker}.`;
|
||||
}
|
||||
|
||||
function hostilityWorlds(rules, tier) {
|
||||
const names = rules.planetTypes
|
||||
.filter((t) => t.colonizable && t.hostility === tier)
|
||||
.map((t) => t.name);
|
||||
return names.length ? `${names.join('/')}-class` : `hostility ${tier}`;
|
||||
}
|
||||
|
||||
const FORMATTERS = {
|
||||
armor: (rules, v) => `Hull armor upgraded to ${v.name} plating — hull hit points ×${v.hpMult} (+${pct(v.hpMult)}).`,
|
||||
cloaked: () => 'Ships are fitted with a stealth field, cloaking them from enemy sensors.',
|
||||
colonizeHostility: (rules, v) => `Colonists can now settle ${hostilityWorlds(rules, v)} worlds.`,
|
||||
counterEspionage: (rules, v) => `+${v} counter-espionage — makes it harder for enemy agents to steal your technology.`,
|
||||
engine: (rules, v) => `Engines upgraded to ${v.name} Drive (speed ${v.speed}).`,
|
||||
espionage: (rules, v) => `+${v} espionage — improves your odds of stealing enemy technology.`,
|
||||
factoryCostMult: (rules, v) => `Factories cost ${pct(v)} less to build.`,
|
||||
fuelRange: (rules, v) => `Fuel range extended to ${v} parsecs — fleets can be ordered further from friendly colonies and starbases.`,
|
||||
groundAttack: (rules, v) => `+${v} ground attack — improves the odds of a successful invasion.`,
|
||||
groundDefense: (rules, v) => `+${v} ground defense — colonies field more defenders when invaded.`,
|
||||
initiative: (rules, v) => `+${v} initiative — fleets choose their engagement range before slower opponents each combat round.`,
|
||||
maxPopBonus: (rules, v) => `+${v} maximum population per colony.`,
|
||||
planetaryShield: (rules, v) => (
|
||||
`+${v} planetary shield — cuts population lost to bombardment by about ${Math.round((1 - 1 / (1 + v * 0.15)) * 100)}%.`
|
||||
),
|
||||
redirectInFlight: () => 'Fleets already in transit can be redirected to a new destination.',
|
||||
refitCostMult: (rules, v) => `Auto-refit costs reduced by ${pct(v)}.`,
|
||||
repairPerRound: (rules, v) => `Ships self-repair ${Math.round(v * 100)}% of max hull every combat round.`,
|
||||
researchMult: (rules, v) => `Research output increased by ${pct(v)} empire-wide.`,
|
||||
scanRange: (rules, v) => `+${v} scanner range for detecting distant fleets and systems.`,
|
||||
shield: (rules, v) => `Deflector shields absorb ${v} point${v === 1 ? '' : 's'} of damage from every incoming hit, on ships and planetary defenses alike.`,
|
||||
singularity: () => "A breakthrough in exotic physics — the foundation for the galaxy's ultimate weapon.",
|
||||
targeting: (rules, v) => `Combat accuracy increased by ${Math.round(v * 6)}% (better targeting computers).`,
|
||||
wasteMult: (rules, v) => `Industrial waste reduced by ${pct(v)}.`,
|
||||
weapon: (rules, v) => describeWeapon(v),
|
||||
};
|
||||
|
||||
/** Turns a tech's `effects` object into an array of full sentences, one per effect. */
|
||||
export function describeTechEffects(rules, effects) {
|
||||
return Object.entries(effects ?? {})
|
||||
.map(([key, value]) => FORMATTERS[key]?.(rules, value))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
// MasterOfVegaGame.js/VegaScreens.js keeps those files from ballooning.
|
||||
|
||||
import { habitableForEmpire } from './VegaLogic.js';
|
||||
import { describeTechEffects } from './VegaTechEffects.js';
|
||||
|
||||
// Events worth interrupting the player for. Everything else (refit,
|
||||
// spyCaught, techStolen, invasionFailed, leaderHired, victory — which
|
||||
|
|
@ -99,9 +100,8 @@ function describeTechDone(rules, state, ev) {
|
|||
const emp = state.empires[ev.empire];
|
||||
const gate = rules.techGates[ev.techId] ?? { buildings: [], prereqOf: [] };
|
||||
const lines = [line(tech.desc, '#9fb6cc')];
|
||||
const effects = Object.entries(tech.effects ?? {});
|
||||
if (effects.length) {
|
||||
lines.push(line(`Effects: ${effects.map(([k, v]) => `${k} ${v > 0 ? '+' : ''}${v}`).join(', ')}`, '#7fd8a0'));
|
||||
for (const effectLine of describeTechEffects(rules, tech.effects)) {
|
||||
lines.push(line(effectLine, '#7fd8a0'));
|
||||
}
|
||||
const newBuildingIds = gate.buildings.filter((id) => rules.buildings[id]);
|
||||
const newTechs = gate.prereqOf
|
||||
|
|
@ -115,7 +115,9 @@ function describeTechDone(rules, state, ev) {
|
|||
return {
|
||||
headline: `Research completed: ${tech.name}.`,
|
||||
lines,
|
||||
buildingUnlocks: newBuildingIds.map((id) => ({ id, name: rules.buildings[id].name })),
|
||||
buildingUnlocks: newBuildingIds.map((id) => ({
|
||||
id, name: rules.buildings[id].name, desc: rules.buildings[id].desc,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ const ROW_BG = 0x142238;
|
|||
// fallback ladder), a building just its one icon.
|
||||
const MEDIA_SIZE = 72;
|
||||
const HULL_ICON_SIZE = 52;
|
||||
const SHIP_MEDIA_W = 150;
|
||||
const BUILDING_MEDIA_W = 90;
|
||||
|
||||
// Event types whose row gets a "View Star System" shortcut — anything
|
||||
|
|
@ -95,7 +94,10 @@ export function openTurnReportScreen(scene, rules, state, events, onClose) {
|
|||
const linesTop = cy;
|
||||
const isBuilding = ev.type === 'buildingDone';
|
||||
const isShip = ev.type === 'shipDone';
|
||||
const mediaW = isShip ? SHIP_MEDIA_W : (isBuilding ? BUILDING_MEDIA_W : 0);
|
||||
// A ship's media block now sits below its text (full-width synopsis);
|
||||
// only a building's icon still sits beside its text, so only that
|
||||
// case needs to reserve wrap width off to the side.
|
||||
const mediaW = isBuilding ? BUILDING_MEDIA_W : 0;
|
||||
for (const ln of desc.lines) {
|
||||
const t = scene.add.text(16, cy, ln.text, {
|
||||
fontFamily: FONT, fontSize: '15px', color: ln.color ?? '#9fb6cc',
|
||||
|
|
@ -116,7 +118,15 @@ export function openTurnReportScreen(scene, rules, state, events, onClose) {
|
|||
fontFamily: FONT, fontSize: '15px', color: '#ffd88a',
|
||||
});
|
||||
col.content.add(label);
|
||||
cy += label.height + 8;
|
||||
cy += label.height + 6;
|
||||
|
||||
if (bu.desc) {
|
||||
const buildingDesc = scene.add.text(16, cy, bu.desc, {
|
||||
fontFamily: FONT, fontSize: '13px', color: '#9fb6cc', wordWrap: { width: w - 32 },
|
||||
});
|
||||
col.content.add(buildingDesc);
|
||||
cy += buildingDesc.height + 8;
|
||||
}
|
||||
|
||||
const status = scene.add.text(16, cy + 34, '', {
|
||||
fontFamily: FONT, fontSize: '13px', color: '#7fd8a0', wordWrap: { width: w - 32 },
|
||||
|
|
@ -145,55 +155,61 @@ export function openTurnReportScreen(scene, rules, state, events, onClose) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (isBuilding || isShip) {
|
||||
// A building's picture sits right-aligned beside its synopsis text
|
||||
// (mediaW reserved off the wrap width above keeps long descriptions
|
||||
// from running under it). A ship's hull icon + commander portrait sit
|
||||
// below its (full-width) synopsis text instead, left-aligned to match
|
||||
// the text above them.
|
||||
if (isBuilding) {
|
||||
cy = Math.max(cy, linesTop + MEDIA_SIZE);
|
||||
const midY = linesTop + MEDIA_SIZE / 2;
|
||||
if (isBuilding) {
|
||||
const icon = scene.add.image(
|
||||
w - 8 - MEDIA_SIZE / 2, midY, scene.art.buildings, buildingFrame(rules, ev.buildingId),
|
||||
).setDisplaySize(MEDIA_SIZE, MEDIA_SIZE);
|
||||
col.content.add(icon);
|
||||
} else {
|
||||
const emp = state.empires[ev.empire];
|
||||
const portrait = makeCommanderPortrait(
|
||||
scene, rules, scene.art, emp.speciesId, ev.hullId,
|
||||
w - 8 - MEDIA_SIZE / 2, midY, MEDIA_SIZE,
|
||||
);
|
||||
col.content.add(portrait);
|
||||
const hullLeft = w - 8 - MEDIA_SIZE - 8 - HULL_ICON_SIZE;
|
||||
const hullIcon = makeShipIcon(
|
||||
scene, rules, scene.art, emp.speciesId, ev.hullId,
|
||||
hullLeft + HULL_ICON_SIZE / 2, midY, HULL_ICON_SIZE,
|
||||
);
|
||||
col.content.add(hullIcon);
|
||||
const icon = scene.add.image(
|
||||
w - 8 - MEDIA_SIZE / 2, midY, scene.art.buildings, buildingFrame(rules, ev.buildingId),
|
||||
).setDisplaySize(MEDIA_SIZE, MEDIA_SIZE);
|
||||
col.content.add(icon);
|
||||
} else if (isShip) {
|
||||
const mediaTop = cy;
|
||||
const midY = mediaTop + MEDIA_SIZE / 2;
|
||||
const emp = state.empires[ev.empire];
|
||||
const hullLeft = 16;
|
||||
const hullIcon = makeShipIcon(
|
||||
scene, rules, scene.art, emp.speciesId, ev.hullId,
|
||||
hullLeft + HULL_ICON_SIZE / 2, midY, HULL_ICON_SIZE,
|
||||
);
|
||||
col.content.add(hullIcon);
|
||||
const portraitLeft = hullLeft + HULL_ICON_SIZE + 8;
|
||||
const portrait = makeCommanderPortrait(
|
||||
scene, rules, scene.art, emp.speciesId, ev.hullId,
|
||||
portraitLeft + MEDIA_SIZE / 2, midY, MEDIA_SIZE,
|
||||
);
|
||||
col.content.add(portrait);
|
||||
|
||||
// Same pop-over the fleet side panel opens on a ship row click
|
||||
// (VegaSidePanel.detailHit → openShipDetail). It draws its own
|
||||
// veil above this modal's depth and doesn't go through
|
||||
// scene.openModal, so it's safe to open without closing the
|
||||
// report first. The clip already playing here pauses while the
|
||||
// pop-over runs its own copy at full size, same as the side
|
||||
// panel's pool does — otherwise the same clip decodes twice.
|
||||
const isVideoPortrait = portrait.type === 'Video';
|
||||
const hit = scene.add.rectangle(hullLeft, linesTop, w - 8 - hullLeft, MEDIA_SIZE, 0xffffff, 0.001)
|
||||
.setOrigin(0, 0).setInteractive({ useHandCursor: true });
|
||||
hit.on('pointerup', (p) => {
|
||||
if (!col.contains(p) || shipDetail) return;
|
||||
if (isVideoPortrait && portrait.scene) portrait.pause?.();
|
||||
shipDetail = openShipDetail(
|
||||
scene, rules, state, scene.art,
|
||||
{ empireIdx: ev.empire, hullId: ev.hullId },
|
||||
() => {
|
||||
shipDetail = null;
|
||||
if (isVideoPortrait && portrait.scene) portrait.resume?.();
|
||||
},
|
||||
);
|
||||
});
|
||||
col.content.add(hit);
|
||||
}
|
||||
// Same pop-over the fleet side panel opens on a ship row click
|
||||
// (VegaSidePanel.detailHit → openShipDetail). It draws its own
|
||||
// veil above this modal's depth and doesn't go through
|
||||
// scene.openModal, so it's safe to open without closing the
|
||||
// report first. The clip already playing here pauses while the
|
||||
// pop-over runs its own copy at full size, same as the side
|
||||
// panel's pool does — otherwise the same clip decodes twice.
|
||||
const isVideoPortrait = portrait.type === 'Video';
|
||||
const hit = scene.add.rectangle(
|
||||
hullLeft, mediaTop, portraitLeft + MEDIA_SIZE - hullLeft, MEDIA_SIZE, 0xffffff, 0.001,
|
||||
).setOrigin(0, 0).setInteractive({ useHandCursor: true });
|
||||
hit.on('pointerup', (p) => {
|
||||
if (!col.contains(p) || shipDetail) return;
|
||||
if (isVideoPortrait && portrait.scene) portrait.pause?.();
|
||||
shipDetail = openShipDetail(
|
||||
scene, rules, state, scene.art,
|
||||
{ empireIdx: ev.empire, hullId: ev.hullId },
|
||||
() => {
|
||||
shipDetail = null;
|
||||
if (isVideoPortrait && portrait.scene) portrait.resume?.();
|
||||
},
|
||||
);
|
||||
});
|
||||
col.content.add(hit);
|
||||
|
||||
cy = mediaTop + MEDIA_SIZE + 10;
|
||||
}
|
||||
|
||||
// Discovery and production rows get a shortcut straight to the
|
||||
|
|
|
|||
Loading…
Reference in New Issue