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/AllianceWarSystem.js
2026-03-11 00:32:45 -03:00

208 lines
7.7 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.

/**
* AllianceWarSystem — GDD Phase 3 Launch Prep
*
* 72-hour scheduled alliance wars. Alliances declare war on each other.
* War score = fleet battles won. Territory bonuses for winning alliance.
* Each war cycles through: declaration (24h) → active (72h) → resolution.
*/
const mongoose = require('mongoose');
const warSchema = new mongoose.Schema({
warId: { type: String, required: true, unique: true },
attackerId: { type: String, required: true },
attackerName: { type: String, required: true },
attackerTag: { type: String, required: true },
defenderId: { type: String, required: true },
defenderName: { type: String, required: true },
defenderTag: { type: String, required: true },
status: { type: String, default: 'declaration', enum: ['declaration','active','ended'] },
attackerScore: { type: Number, default: 0 },
defenderScore: { type: Number, default: 0 },
battles: [{ type: mongoose.Schema.Types.Mixed }],
declaredAt: { type: Date, default: Date.now },
startedAt: { type: Date, default: null },
endsAt: { type: Date, default: null },
winnerId: { type: String, default: null },
winnerName: { type: String, default: null },
territory: { type: mongoose.Schema.Types.Mixed, default: null },
}, { timestamps: true });
let AllianceWar;
try { AllianceWar = mongoose.model('AllianceWar'); }
catch(e) { AllianceWar = mongoose.model('AllianceWar', warSchema); }
class AllianceWarSystem {
constructor() {
this.io = null;
// Territory bonus applied to winning alliance members for 7 days
this.TERRITORY_BONUSES = {
resource_production: 0.10, // +10% all resource production
market_fee_reduction: 0.005, // 0.5% market fee
xp_bonus: 0.05, // +5% XP gain
};
this.DECLARATION_HOURS = 24;
this.WAR_HOURS = 72;
}
setIO(io) { this.io = io; }
// ── Declare War ───────────────────────────────────────────────────
async declareWar(attackerAlliance, defenderAlliance) {
if (!attackerAlliance || !defenderAlliance) throw new Error('Invalid alliances');
if (attackerAlliance._id.toString() === defenderAlliance._id.toString()) {
throw new Error('Cannot declare war on your own alliance');
}
// Check no existing active war between these two
const existing = await AllianceWar.findOne({
status: { $in: ['declaration','active'] },
$or: [
{ attackerId: attackerAlliance._id.toString(), defenderId: defenderAlliance._id.toString() },
{ attackerId: defenderAlliance._id.toString(), defenderId: attackerAlliance._id.toString() },
]
});
if (existing) throw new Error('A war between these alliances is already in progress');
const warId = `war_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
const declaredAt = new Date();
const startedAt = new Date(declaredAt.getTime() + this.DECLARATION_HOURS * 3600 * 1000);
const endsAt = new Date(startedAt.getTime() + this.WAR_HOURS * 3600 * 1000);
const war = await AllianceWar.create({
warId,
attackerId: attackerAlliance._id.toString(),
attackerName: attackerAlliance.name,
attackerTag: attackerAlliance.tag,
defenderId: defenderAlliance._id.toString(),
defenderName: defenderAlliance.name,
defenderTag: defenderAlliance.tag,
status: 'declaration',
declaredAt, startedAt, endsAt,
});
// Schedule auto-start and auto-end
const toStart = startedAt.getTime() - Date.now();
const toEnd = endsAt.getTime() - Date.now();
if (toStart > 0) setTimeout(() => this._activateWar(warId), toStart);
if (toEnd > 0) setTimeout(() => this._resolveWar(warId), toEnd);
return war;
}
async _activateWar(warId) {
const war = await AllianceWar.findOne({ warId });
if (!war || war.status !== 'declaration') return;
war.status = 'active';
await war.save();
if (this.io) this.io.emit('alliance_war_started', { warId, attackerTag: war.attackerTag, defenderTag: war.defenderTag });
}
async _resolveWar(warId) {
const war = await AllianceWar.findOne({ warId });
if (!war || war.status === 'ended') return;
war.status = 'ended';
if (war.attackerScore > war.defenderScore) {
war.winnerId = war.attackerId;
war.winnerName = war.attackerName;
} else if (war.defenderScore > war.attackerScore) {
war.winnerId = war.defenderId;
war.winnerName = war.defenderName;
} else {
war.winnerName = 'Draw';
}
// Territory bonus for winning alliance (stored as metadata on war doc)
if (war.winnerId) {
war.territory = {
allianceId: war.winnerId,
bonuses: this.TERRITORY_BONUSES,
expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000),
};
}
await war.save();
if (this.io) {
this.io.emit('alliance_war_ended', {
warId,
attackerTag: war.attackerTag, attackerScore: war.attackerScore,
defenderTag: war.defenderTag, defenderScore: war.defenderScore,
winnerName: war.winnerName,
territoryBonus: war.territory ? this.TERRITORY_BONUSES : null,
});
}
}
// ── Battle Submission ──────────────────────────────────────────────
async submitBattle(warId, attackerUserId, defenderUserId, attackerWon, battleDetails = {}) {
const war = await AllianceWar.findOne({ warId, status: 'active' });
if (!war) throw new Error('War not found or not active');
const battle = {
attackerUserId, defenderUserId, attackerWon,
at: new Date(), ...battleDetails,
};
war.battles.push(battle);
// Score: 1 point per win for the winning side
if (attackerWon) war.attackerScore += 1;
else war.defenderScore += 1;
await war.save();
if (this.io) {
this.io.emit('alliance_war_battle', {
warId,
attackerTag: war.attackerTag, attackerScore: war.attackerScore,
defenderTag: war.defenderTag, defenderScore: war.defenderScore,
battle,
});
}
return war;
}
// ── Queries ────────────────────────────────────────────────────────
async getActiveWars() {
return AllianceWar.find({ status: { $in: ['declaration','active'] } }).sort({ declaredAt: -1 }).limit(20);
}
async getWarsByAlliance(allianceId) {
return AllianceWar.find({
$or: [{ attackerId: allianceId }, { defenderId: allianceId }]
}).sort({ declaredAt: -1 }).limit(10);
}
async getWar(warId) {
return AllianceWar.findOne({ warId });
}
async getTerritoryBonus(allianceId) {
const now = new Date();
const war = await AllianceWar.findOne({
status: 'ended',
winnerId: allianceId,
'territory.expiresAt': { $gt: now },
});
return war ? war.territory.bonuses : null;
}
// Resume timers on server restart
async resumeTimers() {
const wars = await AllianceWar.find({ status: { $in: ['declaration','active'] } });
const now = Date.now();
for (const war of wars) {
if (war.status === 'declaration' && war.startedAt > new Date()) {
const ms = war.startedAt.getTime() - now;
if (ms > 0) setTimeout(() => this._activateWar(war.warId), ms);
}
if (war.endsAt > new Date()) {
const ms = war.endsAt.getTime() - now;
if (ms > 0) setTimeout(() => this._resolveWar(war.warId), ms);
}
}
}
}
module.exports = AllianceWarSystem;