This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/GameServer/systems/GalaxySystem.js
2026-03-11 00:32:45 -03:00

143 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Galaxy Strike Online - Galaxy System (GDD §8)
* Procedurally generates and manages the 30×20 sector grid.
* Visibility radius is driven by sensor_array building level + research sensorRange bonus.
*/
class GalaxySystem {
constructor() {
this.GRID_W = 30;
this.GRID_H = 20;
this._sectors = null;
}
/** Lazy-generate the galaxy grid once per server process */
getSectors() {
if (!this._sectors) this._sectors = this._generate();
return this._sectors;
}
_generate() {
const sectors = {};
for (let y = 0; y < this.GRID_H; y++) {
for (let x = 0; x < this.GRID_W; x++) {
const id = `${x}_${y}`;
const distFromCenter = Math.hypot(x - 15, y - 10) / 18;
const typeRoll = Math.random();
let type = 'empty';
if (typeRoll > 0.70) type = 'asteroid';
if (typeRoll > 0.88) type = 'npc_territory';
if (typeRoll > 0.94) type = 'trade_hub';
if (typeRoll > 0.97 && distFromCenter > 0.4) type = 'ruins';
if (typeRoll > 0.99 && distFromCenter > 0.6) type = 'void_rift';
const richness = Math.random();
sectors[id] = {
id, x, y, type,
name: this._sectorName(x, y),
threat: Math.min(10, Math.floor(distFromCenter * 10 + Math.random() * 3)),
richness: type === 'asteroid' ? (0.3 + richness * 0.7) : 0,
owner: null,
explored: false,
};
}
}
// Always make the start sector safe
sectors['15_10'] = {
id: '15_10', x: 15, y: 10, type: 'trade_hub',
name: 'New Haven', threat: 0, richness: 0, owner: null, explored: true,
};
return sectors;
}
_sectorName(x, y) {
const prefixes = ['Alpha','Beta','Gamma','Delta','Sigma','Tau','Zeta','Omega','Nova','Vega','Lyra','Cygni'];
const suffixes = ['Prime','Station','Reach','Deep','Expanse','Crossing','Rift','Gate','Void','Fields'];
const seed = (x * 31 + y * 17) % (prefixes.length * suffixes.length);
return prefixes[seed % prefixes.length] + ' ' + suffixes[Math.floor(seed / prefixes.length) % suffixes.length];
}
getSector(id) { return this.getSectors()[id] || null; }
/**
* Mark a sector explored by a player.
* visRadius controls how many rings of adjacent sectors are also added to exploredSectors.
* Default radius 1 = only direct neighbours revealed.
*/
exploreSector(sectorId, playerData, visRadius = 1) {
const sector = this.getSector(sectorId);
if (!sector) throw new Error('Sector not found');
const explored = new Set(playerData.exploredSectors || []);
explored.add(sectorId);
// Reveal all sectors within visRadius rings
const [cx, cy] = sectorId.split('_').map(Number);
for (let dy = -visRadius; dy <= visRadius; dy++) {
for (let dx = -visRadius; dx <= visRadius; dx++) {
if (Math.abs(dx) + Math.abs(dy) > visRadius) continue; // diamond shape
const nid = `${cx + dx}_${cy + dy}`;
if (this.getSector(nid)) explored.add(nid);
}
}
playerData.exploredSectors = Array.from(explored);
return sector;
}
/**
* Return all sectors visible to a player: explored + one ring of fog neighbours.
* visRadius from sensor_array building + research is already applied by exploreSector;
* here we add one extra fog ring beyond whatever has been explored.
*/
getVisibleSectors(playerData, visRadius = 1) {
const explored = new Set(playerData.exploredSectors || ['15_10']);
const visible = new Set(explored);
// One fog ring beyond all explored sectors
for (const id of explored) {
const [x, y] = id.split('_').map(Number);
for (const [dx, dy] of [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[1,-1],[-1,1],[1,1]]) {
const nid = `${x + dx}_${y + dy}`;
if (this.getSector(nid)) visible.add(nid);
}
}
const all = this.getSectors();
return Array.from(visible).map(id => {
const s = { ...all[id] };
s.explored = explored.has(id);
if (!s.explored) {
// Fog-of-war: hide details for unreached but adjacent sectors
s.name = '???';
s.richness = 0;
s.owner = null;
s.type = 'unknown';
s.threat = null;
}
return s;
});
}
/** Player claims a sector as home base */
claimSector(sectorId, playerData) {
const sector = this.getSector(sectorId);
if (!sector) throw new Error('Sector not found');
if (sector.type === 'void_rift') throw new Error('Cannot claim Void Rift sectors');
sector.owner = playerData.userId;
this.exploreSector(sectorId, playerData);
playerData.homeSector = sectorId;
return sector;
}
/** Calculate travel time between two sector IDs using the GDD formula */
travelSeconds(fromId, toId, fleetSpeed = 10, travelSpeedBonus = 0) {
const [fx, fy] = fromId.split('_').map(Number);
const [tx, ty] = toId.split('_').map(Number);
const distance = Math.hypot(tx - fx, ty - fy);
const effectiveSpeed = fleetSpeed * (1 + travelSpeedBonus / 100);
return Math.max(10, Math.round((distance * 300) / Math.max(1, effectiveSpeed)));
}
}
module.exports = GalaxySystem;