122 lines
4.6 KiB
JavaScript
122 lines
4.6 KiB
JavaScript
/**
|
|
* Galaxy Strike Online — Season System (GDD §20.3)
|
|
* 90-day seasons with themed content, seasonal leaderboards, and exclusive rewards.
|
|
* Season 1: "Dawn of the Void"
|
|
*/
|
|
|
|
const SEASONS = {
|
|
1: {
|
|
id: 1,
|
|
name: 'Dawn of the Void',
|
|
theme: 'void_cult',
|
|
icon: '🌑',
|
|
color: '#78909c',
|
|
description: 'The Void Cult stirs. Ancient relics emerge from uncharted sectors. Are you ready?',
|
|
durationDays: 90,
|
|
rewards: {
|
|
top1: { title: 'Void Champion', skin: 'void_champion_skin', credits: 50000 },
|
|
top10: { title: 'Void Knight', skin: 'void_knight_skin', credits: 20000 },
|
|
top100:{ title: 'Void Initiate', skin: null, credits: 5000 },
|
|
participation: { title: 'Season 1 Veteran', credits: 1000 },
|
|
},
|
|
bonuses: {
|
|
darkMatterBonus: 0.25, // +25% dark matter production
|
|
voidCultRepBonus: 0.5, // +50% Void Cult reputation gain
|
|
},
|
|
startDate: new Date('2026-01-01'),
|
|
endDate: new Date('2026-04-01'),
|
|
}
|
|
};
|
|
|
|
const CURRENT_SEASON_ID = 1;
|
|
|
|
class SeasonSystem {
|
|
constructor() {
|
|
this.currentSeason = SEASONS[CURRENT_SEASON_ID];
|
|
}
|
|
|
|
getCurrentSeason() {
|
|
const now = new Date();
|
|
const s = this.currentSeason;
|
|
if (!s) return { active: false };
|
|
const daysLeft = Math.max(0, Math.ceil((s.endDate - now) / 86400000));
|
|
const daysTotal = Math.ceil((s.endDate - s.startDate) / 86400000);
|
|
const daysDone = Math.max(0, Math.ceil((now - s.startDate) / 86400000));
|
|
return {
|
|
active: now >= s.startDate && now <= s.endDate,
|
|
season: { ...s, daysLeft, daysTotal, daysDone, progress: Math.round(daysDone/daysTotal*100) },
|
|
};
|
|
}
|
|
|
|
getSeasonBonuses() {
|
|
return this.currentSeason?.bonuses || {};
|
|
}
|
|
|
|
recordSeasonActivity(playerData, activityType, score = 1) {
|
|
if (!playerData.seasonProgress) playerData.seasonProgress = {};
|
|
playerData.seasonProgress[CURRENT_SEASON_ID] = playerData.seasonProgress[CURRENT_SEASON_ID] || { score: 0, activities: {} };
|
|
const sp = playerData.seasonProgress[CURRENT_SEASON_ID];
|
|
sp.score += score;
|
|
sp.activities[activityType] = (sp.activities[activityType] || 0) + 1;
|
|
return sp.score;
|
|
}
|
|
|
|
getSeasonScore(playerData) {
|
|
return playerData.seasonProgress?.[CURRENT_SEASON_ID]?.score || 0;
|
|
}
|
|
|
|
/** Check if the season just ended (active last tick, ended now) */
|
|
isActive() {
|
|
const now = Date.now();
|
|
const s = this.currentSeason;
|
|
return s && now >= s.startDate && now <= s.endDate;
|
|
}
|
|
|
|
/**
|
|
* Distribute end-of-season rewards to all players.
|
|
* Called once when season transitions from active -> ended.
|
|
* @param {Array} allPlayerDataArray — array of playerData objects
|
|
*/
|
|
distributeSeasonRewards(allPlayerDataArray) {
|
|
const TIER_REWARDS = [
|
|
{ minRank: 1, maxRank: 1, credits: 100000, title: 'Void Champion', skin: 'season1_champion' },
|
|
{ minRank: 2, maxRank: 3, credits: 50000, title: 'Void Master', skin: 'season1_master' },
|
|
{ minRank: 4, maxRank: 10, credits: 25000, title: 'Void Elite', skin: null },
|
|
{ minRank: 11, maxRank: 50, credits: 10000, title: 'Void Veteran', skin: null },
|
|
{ minRank: 51, maxRank: Infinity, credits: 2500, title: 'Void Recruit', skin: null },
|
|
];
|
|
|
|
// Sort players by season score descending
|
|
const ranked = allPlayerDataArray
|
|
.filter(pd => (pd.seasonProgress?.[CURRENT_SEASON_ID]?.score || 0) > 0)
|
|
.sort((a, b) => this.getSeasonScore(b) - this.getSeasonScore(a));
|
|
|
|
const distributed = [];
|
|
ranked.forEach((pd, idx) => {
|
|
const rank = idx + 1;
|
|
const tier = TIER_REWARDS.find(t => rank >= t.minRank && rank <= t.maxRank);
|
|
if (!tier) return;
|
|
// Apply rewards
|
|
pd.resources = pd.resources || {};
|
|
pd.resources.credits = (pd.resources.credits || 0) + tier.credits;
|
|
pd.stats = pd.stats || {};
|
|
pd.stats.title = tier.title;
|
|
if (tier.skin && pd.cosmetics) {
|
|
pd.cosmetics.unlockedSkins = pd.cosmetics.unlockedSkins || [];
|
|
if (!pd.cosmetics.unlockedSkins.includes(tier.skin)) {
|
|
pd.cosmetics.unlockedSkins.push(tier.skin);
|
|
}
|
|
}
|
|
// Mark season as rewarded
|
|
if (pd.seasonProgress?.[CURRENT_SEASON_ID]) {
|
|
pd.seasonProgress[CURRENT_SEASON_ID].rewarded = true;
|
|
pd.seasonProgress[CURRENT_SEASON_ID].rank = rank;
|
|
}
|
|
distributed.push({ userId: pd.userId || pd._id, rank, credits: tier.credits, title: tier.title });
|
|
});
|
|
|
|
console.log(`[SEASON] Distributed rewards to ${distributed.length} players for season ${CURRENT_SEASON_ID}`);
|
|
return distributed;
|
|
}
|
|
}
|