diff --git a/resources/sprites/nyeve/conquest.png b/resources/sprites/nyeve/conquest.png deleted file mode 100644 index 666e55ea2..000000000 Binary files a/resources/sprites/nyeve/conquest.png and /dev/null differ diff --git a/resources/sprites/nyeve/firework.png b/resources/sprites/nyeve/firework.png deleted file mode 100644 index 7d3a4db53..000000000 Binary files a/resources/sprites/nyeve/firework.png and /dev/null differ diff --git a/resources/sprites/nyeve/firework_cyan.png b/resources/sprites/nyeve/firework_cyan.png deleted file mode 100644 index c51990206..000000000 Binary files a/resources/sprites/nyeve/firework_cyan.png and /dev/null differ diff --git a/resources/sprites/nyeve/firework_red.png b/resources/sprites/nyeve/firework_red.png deleted file mode 100644 index e1b327756..000000000 Binary files a/resources/sprites/nyeve/firework_red.png and /dev/null differ diff --git a/resources/sprites/nyeve/firework_yellow.png b/resources/sprites/nyeve/firework_yellow.png deleted file mode 100644 index 9f59e11e7..000000000 Binary files a/resources/sprites/nyeve/firework_yellow.png and /dev/null differ diff --git a/src/client/components/baseComponents/stats/PlayerStatsTree.ts b/src/client/components/baseComponents/stats/PlayerStatsTree.ts index 4527529a2..c99705076 100644 --- a/src/client/components/baseComponents/stats/PlayerStatsTree.ts +++ b/src/client/components/baseComponents/stats/PlayerStatsTree.ts @@ -33,6 +33,9 @@ export class PlayerStatsTreeView extends LitElement { } private get availableDifficulties(): Difficulty[] { + // For Public games, don't show difficulty selector (we'll combine them) + if (this.selectedType === GameType.Public) return []; + const typeNode = this.statsTree?.[this.selectedType]; const modeNode = typeNode?.[this.selectedMode]; if (!modeNode) return []; @@ -49,11 +52,120 @@ export class PlayerStatsTreeView extends LitElement { return this; } + private addBigIntArrays(a?: bigint[], b?: bigint[]): bigint[] | undefined { + if (!a && !b) return undefined; + if (!a) return b; + if (!b) return a; + + const maxLen = Math.max(a.length, b.length); + const result: bigint[] = []; + for (let i = 0; i < maxLen; i++) { + result[i] = (a[i] ?? 0n) + (b[i] ?? 0n); + } + return result; + } + + private combineDifficultyStats( + modeNode: Record, + ): PlayerStatsLeaf | null { + const difficulties = Object.keys(modeNode).filter(isDifficulty); + if (difficulties.length === 0) return null; + + // Start with zeros + let combinedWins = 0n; + let combinedLosses = 0n; + let combinedTotal = 0n; + const combinedStats: PlayerStats = {}; + + // Aggregate across all difficulties + for (const diff of difficulties) { + const leaf = modeNode[diff as Difficulty]; + if (!leaf) continue; + + combinedWins += leaf.wins; + combinedLosses += leaf.losses; + combinedTotal += leaf.total; + + if (leaf.stats) { + // Combine array-based stats + combinedStats.attacks = this.addBigIntArrays( + combinedStats.attacks, + leaf.stats.attacks, + ); + combinedStats.gold = this.addBigIntArrays( + combinedStats.gold, + leaf.stats.gold, + ); + + // Combine scalar stats + if (leaf.stats.betrayals !== undefined) { + combinedStats.betrayals = + (combinedStats.betrayals ?? 0n) + leaf.stats.betrayals; + } + if (leaf.stats.killedAt !== undefined) { + combinedStats.killedAt = + (combinedStats.killedAt ?? 0n) + leaf.stats.killedAt; + } + if (leaf.stats.conquests !== undefined) { + combinedStats.conquests = + (combinedStats.conquests ?? 0n) + leaf.stats.conquests; + } + + // Combine boats stats (nested object) + if (leaf.stats.boats) { + combinedStats.boats ??= {}; + for (const [boatType, values] of Object.entries(leaf.stats.boats)) { + combinedStats.boats[boatType] = this.addBigIntArrays( + combinedStats.boats[boatType], + values, + ); + } + } + + // Combine bombs stats (nested object) + if (leaf.stats.bombs) { + combinedStats.bombs ??= {}; + for (const [bombType, values] of Object.entries(leaf.stats.bombs)) { + combinedStats.bombs[bombType] = this.addBigIntArrays( + combinedStats.bombs[bombType], + values, + ); + } + } + + // Combine units stats (nested object) + if (leaf.stats.units) { + combinedStats.units ??= {}; + for (const [unitType, values] of Object.entries(leaf.stats.units)) { + combinedStats.units[unitType] = this.addBigIntArrays( + combinedStats.units[unitType], + values, + ); + } + } + } + } + + return { + wins: combinedWins, + losses: combinedLosses, + total: combinedTotal, + stats: combinedStats, + }; + } + private getSelectedLeaf(): PlayerStatsLeaf | null { const typeNode = this.statsTree?.[this.selectedType]; if (!typeNode) return null; const modeNode = typeNode[this.selectedMode]; if (!modeNode) return null; + + // For Public games, combine all difficulties + if (this.selectedType === GameType.Public) { + return this.combineDifficultyStats(modeNode); + } + + // For Private and Singleplayer, use the selected difficulty const diffNode = modeNode[this.selectedDifficulty]; if (!diffNode) return null; return diffNode; diff --git a/src/client/graphics/AnimatedSpriteLoader.ts b/src/client/graphics/AnimatedSpriteLoader.ts index 9ee77cdcf..03e49e0cc 100644 --- a/src/client/graphics/AnimatedSpriteLoader.ts +++ b/src/client/graphics/AnimatedSpriteLoader.ts @@ -5,11 +5,6 @@ import dust from "../../../resources/sprites/dust.png"; import miniExplosion from "../../../resources/sprites/miniExplosion.png"; import miniFire from "../../../resources/sprites/minifire.png"; import nuke from "../../../resources/sprites/nukeExplosion.png"; -import conquestChampagne from "../../../resources/sprites/nyeve/conquest.png"; -import nukeEve from "../../../resources/sprites/nyeve/firework.png"; -import nukeEveCyan from "../../../resources/sprites/nyeve/firework_cyan.png"; -import nukeEveRed from "../../../resources/sprites/nyeve/firework_red.png"; -import nukeEveYellow from "../../../resources/sprites/nyeve/firework_yellow.png"; import SAMExplosion from "../../../resources/sprites/samExplosion.png"; import sinkingShip from "../../../resources/sprites/sinkingShip.png"; import miniSmoke from "../../../resources/sprites/smoke.png"; @@ -140,51 +135,6 @@ const ANIMATED_SPRITE_CONFIG: Partial> = { originX: 10, originY: 16, }, - [FxType.ConquestChampagne]: { - url: conquestChampagne, - frameWidth: 28, - frameCount: 8, - frameDuration: 90, - looping: false, - originX: 14, - originY: 23, - }, - [FxType.FireworkAll]: { - url: nukeEve, - frameWidth: 60, - frameCount: 15, - frameDuration: 90, - looping: false, - originX: 30, - originY: 30, - }, - [FxType.FireworkRed]: { - url: nukeEveRed, - frameWidth: 30, - frameCount: 9, - frameDuration: 100, - looping: false, - originX: 15, - originY: 20, - }, - [FxType.FireworkCyan]: { - url: nukeEveCyan, - frameWidth: 30, - frameCount: 13, - frameDuration: 100, - looping: false, - originX: 15, - originY: 20, - }, - [FxType.FireworkYellow]: { - url: nukeEveYellow, - frameWidth: 30, - frameCount: 15, - frameDuration: 100, - looping: false, - originX: 15, - originY: 20, - }, }; export class AnimatedSpriteLoader { diff --git a/src/client/graphics/fx/ConquestFx.ts b/src/client/graphics/fx/ConquestFx.ts index a55ca77c7..7fa8d0690 100644 --- a/src/client/graphics/fx/ConquestFx.ts +++ b/src/client/graphics/fx/ConquestFx.ts @@ -25,7 +25,7 @@ export function conquestFxFactory( animatedSpriteLoader, x, y, - FxType.ConquestChampagne, + FxType.Conquest, 2500, ); const fadeAnimation = new FadeFx(swordAnimation, 0.1, 0.6); diff --git a/src/client/graphics/fx/Fx.ts b/src/client/graphics/fx/Fx.ts index 37fd4108f..d4b206614 100644 --- a/src/client/graphics/fx/Fx.ts +++ b/src/client/graphics/fx/Fx.ts @@ -16,9 +16,4 @@ export enum FxType { UnderConstruction = "UnderConstruction", Dust = "Dust", Conquest = "Conquest", - FireworkAll = "FireworkAll", - FireworkRed = "FireworkRed", - FireworkYellow = "FireworkYellow", - FireworkCyan = "FireworkCyan", - ConquestChampagne = "ConquestChampagne", } diff --git a/src/client/graphics/fx/NukeFx.ts b/src/client/graphics/fx/NukeFx.ts index 63d657011..479d68e18 100644 --- a/src/client/graphics/fx/NukeFx.ts +++ b/src/client/graphics/fx/NukeFx.ts @@ -55,7 +55,7 @@ function addSpriteInCircle( game.isLand(game.ref(spawnX, spawnY)) ) { const sprite = new FadeFx( - new SpriteFx(animatedSpriteLoader, spawnX, spawnY, type), + new SpriteFx(animatedSpriteLoader, spawnX, spawnY, type, 6000), 0.1, 0.8, ); @@ -79,16 +79,19 @@ export function nukeFxFactory( ): Fx[] { const nukeFx: Fx[] = []; // Explosion animation - nukeFx.push(new SpriteFx(animatedSpriteLoader, x, y, FxType.FireworkAll)); + nukeFx.push(new SpriteFx(animatedSpriteLoader, x, y, FxType.Nuke)); + // Shockwave animation + nukeFx.push(new ShockwaveFx(x, y, 1500, radius * 1.5)); // Ruins and desolation sprites const debrisPlan: Array<{ type: FxType; radiusFactor: number; density: number; }> = [ - { type: FxType.FireworkRed, radiusFactor: 1.0, density: 1 / 28 }, - { type: FxType.FireworkCyan, radiusFactor: 0.9, density: 1 / 70 }, - { type: FxType.FireworkYellow, radiusFactor: 0.9, density: 1 / 70 }, + { type: FxType.MiniFire, radiusFactor: 1.0, density: 1 / 25 }, + { type: FxType.MiniSmoke, radiusFactor: 1.0, density: 1 / 28 }, + { type: FxType.MiniBigSmoke, radiusFactor: 0.9, density: 1 / 70 }, + { type: FxType.MiniSmokeAndFire, radiusFactor: 0.9, density: 1 / 70 }, ]; for (const { type, radiusFactor, density } of debrisPlan) { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index a9b633d29..ad535bbe9 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -364,7 +364,7 @@ export class DefaultConfig implements Config { // Sigmoid: concave start, sharp S-curve middle, linear end - heavily punishes trades under range debuff. const debuff = this.tradeShipShortRangeDebuff(); const baseGold = - 100_000 / (1 + Math.exp(-0.03 * (dist - debuff))) + 100 * dist; + 100_000 / (1 + Math.exp(-0.03 * (dist - debuff))) + 150 * dist; const numPortBonus = numPorts - 1; // Hyperbolic decay, midpoint at 5 ports, 3x bonus max. const bonus = 1 + 2 * (numPortBonus / (numPortBonus + 5));