refactor(mastervega): remove Tooltip dependency, show unexplored stars, and use tech-aware habitability
- Remove Tooltip import and onStarHover callback from MasterOfVegaGame; system view now stays closed for unexplored stars until a scout arrives. - Make VegaStarMap own its Tooltip via attachTo, replacing the removed onStarHover handler. - Show unexplored stars at all zoom levels (dimmed) so they remain hoverable targets and display an UNEXPLORED tooltip. - Split out habitableForEmpire from canColonize so the star map and tooltips use the same tech-aware habitability check (planet type + current planetology tech) instead of the static colonizable flag. - Change starting exploration: only the home system is explored; all other stars remain unexplored until a scout physically arrives.
This commit is contained in:
parent
7f1cfcc982
commit
a16e59067b
|
|
@ -7,7 +7,6 @@
|
|||
import * as Phaser from 'phaser';
|
||||
import { GAME_HEIGHT, GAME_WIDTH } from '../../config.js';
|
||||
import { Button } from '../../ui/Button.js';
|
||||
import { Tooltip } from '../../ui/Tooltip.js';
|
||||
import { MusicPlayer } from '../../ui/MusicPlayer.js';
|
||||
import { getGameSoundtrack } from '../../services/soundtrack.js';
|
||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||
|
|
@ -53,8 +52,6 @@ export default class MasterOfVegaGame extends Phaser.Scene {
|
|||
console.info(`[MasterOfVega] procedural art for: ${procedural.join(', ')}`);
|
||||
}
|
||||
|
||||
this.tooltip = new Tooltip(this, { depth: 70 });
|
||||
|
||||
try {
|
||||
const { tracks, volume } = getGameSoundtrack(this);
|
||||
if (tracks?.length) this.music = new MusicPlayer(this, tracks, volume);
|
||||
|
|
@ -467,7 +464,6 @@ export default class MasterOfVegaGame extends Phaser.Scene {
|
|||
|
||||
this.map = new VegaStarMap(this, this.rules, this.state, this.art, {
|
||||
onStarClick: (idx) => this.onStarClick(idx),
|
||||
onStarHover: (idx) => this.onStarHover(idx),
|
||||
onFleetClick: (fleet) => this.onFleetClick(fleet),
|
||||
blockWheel: () => this.modalOpen,
|
||||
});
|
||||
|
|
@ -580,6 +576,11 @@ export default class MasterOfVegaGame extends Phaser.Scene {
|
|||
return;
|
||||
}
|
||||
|
||||
// Unexplored systems only get the map's hover tooltip (VegaStarMap owns
|
||||
// it) — the system view stays closed until a scout has actually arrived.
|
||||
const emp = this.state.empires[this.state.humanIndex];
|
||||
if (emp && !emp.explored[idx]) return;
|
||||
|
||||
this.openModal((done) => openSystemView(this, this.rules, this.state, idx, this.art, {
|
||||
viewerIdx: this.state.humanIndex,
|
||||
onChanged: () => this.refreshAll(),
|
||||
|
|
@ -587,13 +588,6 @@ export default class MasterOfVegaGame extends Phaser.Scene {
|
|||
}));
|
||||
}
|
||||
|
||||
onStarHover(idx) {
|
||||
if (idx < 0) return;
|
||||
const star = this.state.galaxy.stars[idx];
|
||||
const emp = this.state.empires[this.state.humanIndex];
|
||||
if (!star || (emp && !emp.explored[idx])) return;
|
||||
}
|
||||
|
||||
onFleetClick(fleet) {
|
||||
if (this.modalOpen || this.busy) return;
|
||||
if (fleet.empireIdx !== this.state.humanIndex) return;
|
||||
|
|
|
|||
|
|
@ -197,11 +197,21 @@ export function colonyGroundDefense(rules, state, colony) {
|
|||
|
||||
// Can this empire settle this planet at all?
|
||||
export function canColonize(rules, state, e, starIdx, orbit) {
|
||||
if (colonyAt(state, starIdx) && coloniesAt(state, starIdx).some((c) => c.orbit === orbit)) return false;
|
||||
return habitableForEmpire(rules, state, e, starIdx, orbit);
|
||||
}
|
||||
|
||||
// Whether this planet is within an empire's current colonisation reach —
|
||||
// type + hostility vs. current planetology tech — regardless of whether it is
|
||||
// already settled. Split out of canColonize so the star map and tooltip's "N
|
||||
// habitable" readout can match what the colony screen actually offers instead
|
||||
// of just the planet type's static colonizable flag (which ignores tech and
|
||||
// made "1 habitable" show even for worlds too hostile to settle yet).
|
||||
export function habitableForEmpire(rules, state, e, starIdx, orbit) {
|
||||
const planet = state.galaxy.stars[starIdx]?.planets?.[orbit];
|
||||
if (!planet) return false;
|
||||
const type = rules.planetTypes[planet.typeId];
|
||||
if (!type.colonizable) return false;
|
||||
if (colonyAt(state, starIdx) && coloniesAt(state, starIdx).some((c) => c.orbit === orbit)) return false;
|
||||
const emp = state.empires[e];
|
||||
const spec = rules.species[emp.speciesId];
|
||||
if (spec.traits.colonizeAnything) return true;
|
||||
|
|
@ -383,12 +393,8 @@ export function createGame(rules, opts) {
|
|||
colony.factories = rules.economy.startingFactories;
|
||||
colony.capital = true;
|
||||
state.empires[e].explored[starIdx] = true;
|
||||
// Neighbours are charted from the start — nobody begins truly blind.
|
||||
for (let i = 0; i < state.galaxy.stars.length; i += 1) {
|
||||
if (parsecs(state.galaxy, starIdx, i) <= (rules.economy.baseFuelRange ?? 4)) {
|
||||
state.empires[e].explored[i] = true;
|
||||
}
|
||||
}
|
||||
// Everything else starts unexplored — a scout has to physically arrive
|
||||
// (see moveFleets' `explored[f.starIdx] = true`) before its details show.
|
||||
// Starting fleet: a scout and a colony ship, plus difficulty handicap ships.
|
||||
const bonus = e === humanIndex ? 0 : diff.aiStartBonus;
|
||||
addFleet(rules, state, e, starIdx, [
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@
|
|||
|
||||
import * as Phaser from 'phaser';
|
||||
import { GAME_HEIGHT, GAME_WIDTH } from '../../config.js';
|
||||
import { Tooltip } from '../../ui/Tooltip.js';
|
||||
import { makeNebula } from './VegaNebula.js';
|
||||
import { PARSEC_PX, parsecs, mulberry32 } from './VegaGalaxyGen.js';
|
||||
import { reachableStars, coloniesAt, empireColonies, fleetEta } from './VegaLogic.js';
|
||||
import {
|
||||
reachableStars, 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';
|
||||
|
||||
const FONT = '"Julius Sans One"';
|
||||
|
||||
|
|
@ -65,6 +69,8 @@ export default class VegaStarMap {
|
|||
|
||||
ensureSoftDisc(scene, 'vega-soft-disc', 256);
|
||||
|
||||
this.tooltip = new Tooltip(scene, { depth: 70 });
|
||||
|
||||
// --- screen-space backdrop
|
||||
this.bgLayer = scene.add.container(0, 0).setDepth(0);
|
||||
this.nebula = makeNebula(scene, this.bgLayer, {
|
||||
|
|
@ -178,6 +184,10 @@ export default class VegaStarMap {
|
|||
hit.on('pointerover', () => { this.hoverStar = star.idx; this.cb.onStarHover?.(star.idx); });
|
||||
hit.on('pointerout', () => { if (this.hoverStar === star.idx) this.hoverStar = -1; this.cb.onStarHover?.(-1); });
|
||||
hit.on('pointerup', (p) => { if (!this.dragged) this.cb.onStarClick?.(star.idx, p); });
|
||||
this.tooltip.attachTo(hit, () => {
|
||||
const viewer = this.viewerIdx >= 0 ? this.state.empires[this.viewerIdx] : null;
|
||||
return describeStarTooltip(this.rules, this.state, viewer, star.idx);
|
||||
});
|
||||
container.add(hit);
|
||||
|
||||
this.starLayer.add(container);
|
||||
|
|
@ -274,7 +284,10 @@ export default class VegaStarMap {
|
|||
const viewer = this.viewerIdx >= 0 ? state.empires[this.viewerIdx] : null;
|
||||
for (const s of this.starSprites) {
|
||||
const explored = !viewer || viewer.explored[s.star.idx];
|
||||
s.container.setVisible(explored || this.isFarZoom);
|
||||
// Unexplored stars stay visible (dimmed) at every zoom level so there is
|
||||
// always something to hover/click for the UNEXPLORED tooltip and a scout
|
||||
// target — only their name, ring and system contents are hidden.
|
||||
s.container.setVisible(true);
|
||||
s.body.setAlpha(explored ? 1 : 0.25);
|
||||
|
||||
const cols = coloniesAt(state, s.star.idx);
|
||||
|
|
@ -394,7 +407,13 @@ export default class VegaStarMap {
|
|||
|
||||
// Closest zoom adds the system's contents.
|
||||
if (this.isNearZoom) {
|
||||
const habitable = s.star.planets.filter((p) => this.rules.planetTypes[p.typeId].colonizable).length;
|
||||
// "Habitable" here means settleable at the viewer's CURRENT
|
||||
// planetology, not just the planet type's static ceiling — a Radiated
|
||||
// world is colonizable in principle but still shows as 0/1 until tech
|
||||
// catches up, matching what the system view's colonize button says.
|
||||
const habitable = viewer
|
||||
? s.star.planets.filter((p, orbit) => habitableForEmpire(this.rules, state, viewer.idx, s.star.idx, orbit)).length
|
||||
: s.star.planets.filter((p) => this.rules.planetTypes[p.typeId].colonizable).length;
|
||||
if (s.star.planets.length) {
|
||||
const sub = this.scene.add.text(
|
||||
s.star.x, s.star.y + s.cls.radius * 2.2 + 32,
|
||||
|
|
@ -533,6 +552,7 @@ export default class VegaStarMap {
|
|||
setViewer(idx) { this.viewerIdx = idx; this.rangeDirty = true; this.refresh(); }
|
||||
|
||||
destroy() {
|
||||
this.tooltip.destroy();
|
||||
this.nebula?.destroy();
|
||||
this.bgLayer.destroy();
|
||||
this.starfield.destroy();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// Master of Vega — hover-tooltip content, brought over from the civilization
|
||||
// game's tooltip system (src/games/civilization/CivilizationTooltips.js) and
|
||||
// adapted for the star map. Rendering itself is the shared src/ui/Tooltip.js
|
||||
// component; these functions only build the {title, lines} content it wants.
|
||||
|
||||
import { COLORS } from '../../config.js';
|
||||
import { coloniesAt, habitableForEmpire } from './VegaLogic.js';
|
||||
|
||||
// Hover tooltip for a star on the map. `viewerEmp` is the human empire (or
|
||||
// null in observer mode, which sees everything). An unexplored star only
|
||||
// ever reveals its class — no name, no colonies, no planets — since that is
|
||||
// the one thing visible from a distance without a scout in the system.
|
||||
export function describeStarTooltip(rules, state, viewerEmp, starIdx) {
|
||||
const star = state.galaxy.stars[starIdx];
|
||||
const cls = rules.starClasses[star.classId];
|
||||
const explored = !viewerEmp || viewerEmp.explored[starIdx];
|
||||
|
||||
if (!explored) {
|
||||
return {
|
||||
title: 'UNEXPLORED',
|
||||
titleColor: COLORS.dangerHex,
|
||||
lines: [
|
||||
{ text: `${cls.name} star`, color: COLORS.goldHex },
|
||||
{ text: cls.desc },
|
||||
{ text: 'Send a scout ship to chart this system.', color: COLORS.mutedHex },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = [{ text: `${cls.name} star`, color: COLORS.goldHex }];
|
||||
|
||||
const cols = coloniesAt(state, starIdx);
|
||||
for (const c of cols) {
|
||||
const owner = state.empires[c.empireIdx];
|
||||
lines.push({
|
||||
text: `${owner.name} colony — pop ${c.pop.toFixed(1)}${c.capital ? ' (capital)' : ''}`,
|
||||
color: owner.color,
|
||||
});
|
||||
}
|
||||
|
||||
if (star.planets.length) {
|
||||
// Matches the system view's colonize button: habitable at the viewer's
|
||||
// CURRENT planetology, not just the planet type's static ceiling.
|
||||
const habitable = viewerEmp
|
||||
? star.planets.filter((p, orbit) => habitableForEmpire(rules, state, viewerEmp.idx, starIdx, orbit)).length
|
||||
: star.planets.filter((p) => rules.planetTypes[p.typeId].colonizable).length;
|
||||
lines.push({ text: `${star.planets.length} world${star.planets.length === 1 ? '' : 's'} · ${habitable} habitable` });
|
||||
} else {
|
||||
lines.push({ text: 'No planets in this system.', color: COLORS.mutedHex });
|
||||
}
|
||||
|
||||
return { title: star.name, lines };
|
||||
}
|
||||
Loading…
Reference in New Issue