Merge branch 'v28' into crazygames

This commit is contained in:
iamlewis
2026-01-07 17:57:40 +00:00
committed by GitHub
11 changed files with 122 additions and 62 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

@@ -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<string, PlayerStatsLeaf>,
): 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;
@@ -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<Record<FxType, AnimatedSpriteConfig>> = {
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 {
+1 -1
View File
@@ -25,7 +25,7 @@ export function conquestFxFactory(
animatedSpriteLoader,
x,
y,
FxType.ConquestChampagne,
FxType.Conquest,
2500,
);
const fadeAnimation = new FadeFx(swordAnimation, 0.1, 0.6);
-5
View File
@@ -16,9 +16,4 @@ export enum FxType {
UnderConstruction = "UnderConstruction",
Dust = "Dust",
Conquest = "Conquest",
FireworkAll = "FireworkAll",
FireworkRed = "FireworkRed",
FireworkYellow = "FireworkYellow",
FireworkCyan = "FireworkCyan",
ConquestChampagne = "ConquestChampagne",
}
+8 -5
View File
@@ -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) {
+1 -1
View File
@@ -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));