Merge branch 'main' into 2393-show-boat-troops-as-attacking-troops

This commit is contained in:
Rj Manhas
2025-11-19 21:27:22 -07:00
committed by GitHub
56 changed files with 2523 additions and 615 deletions
+21
View File
@@ -90,3 +90,24 @@ export const PlayerProfileSchema = z.object({
stats: PlayerStatsTreeSchema,
});
export type PlayerProfile = z.infer<typeof PlayerProfileSchema>;
export const ClanLeaderboardEntrySchema = z.object({
clanTag: z.string(),
games: z.number(),
wins: z.number(),
losses: z.number(),
playerSessions: z.number(),
weightedWins: z.number(),
weightedLosses: z.number(),
weightedWLRatio: z.number(),
});
export type ClanLeaderboardEntry = z.infer<typeof ClanLeaderboardEntrySchema>;
export const ClanLeaderboardResponseSchema = z.object({
start: z.iso.datetime(),
end: z.iso.datetime(),
clans: ClanLeaderboardEntrySchema.array(),
});
export type ClanLeaderboardResponse = z.infer<
typeof ClanLeaderboardResponseSchema
>;
+140 -188
View File
@@ -42,222 +42,174 @@ function generateTeamColors(baseColor: Colord): Colord[] {
}
export const nationColors: Colord[] = [
colord("rgb(210,210,100)"), // Lime Yellow
colord("rgb(180,210,120)"), // Light Green
colord("rgb(170,190,100)"), // Yellow Green
colord("rgb(80,200,120)"), // Emerald Green
colord("rgb(130,200,130)"), // Light Sea Green
colord("rgb(140,180,140)"), // Dark Sea Green
colord("rgb(160,190,160)"), // Pale Green
colord("rgb(160,180,140)"), // Dark Olive Green
colord("rgb(100,160,80)"), // Olive Green
colord("rgb(100,140,110)"), // Sea Green
colord("rgb(100,180,160)"), // Aquamarine
colord("rgb(130,180,170)"), // Medium Aquamarine
colord("rgb(170,190,180)"), // Pale Blue Green
colord("rgb(100,130,150)"), // Steel Blue
colord("rgb(120,160,200)"), // Cornflower Blue
colord("rgb(140,150,180)"), // Light Slate Gray
colord("rgb(100,210,210)"), // Turquoise
colord("rgb(140,180,220)"), // Light Blue
colord("rgb(130,170,190)"), // Cadet Blue
colord("rgb(100,180,230)"), // Sky Blue
colord("rgb(80,130,190)"), // Navy Blue
colord("rgb(120,120,190)"), // Periwinkle
colord("rgb(150,110,190)"), // Lavender
colord("rgb(160,120,160)"), // Purple Gray
colord("rgb(170,140,190)"), // Medium Purple
colord("rgb(180,130,180)"), // Plum
colord("rgb(190,140,150)"), // Puce
colord("rgb(180,100,230)"), // Purple
colord("rgb(180,160,180)"), // Mauve
colord("rgb(170,150,170)"), // Dusty Rose
colord("rgb(150,130,150)"), // Thistle
colord("rgb(230,180,180)"), // Light Pink
colord("rgb(210,160,200)"), // Orchid
colord("rgb(230,130,180)"), // Pink
colord("rgb(210,100,160)"), // Hot Pink
colord("rgb(190,100,130)"), // Maroon
colord("rgb(220,120,120)"), // Coral
colord("rgb(200,130,110)"), // Dark Salmon
colord("rgb(230,140,140)"), // Salmon
colord("rgb(230,100,100)"), // Bright Red
colord("rgb(100,180,230)"), // Sky Blue
colord("rgb(230,180,80)"), // Golden Yellow
colord("rgb(180,100,230)"), // Purple
colord("rgb(80,200,120)"), // Emerald Green
colord("rgb(230,130,180)"), // Pink
colord("rgb(100,160,80)"), // Olive Green
colord("rgb(230,150,100)"), // Peach
colord("rgb(80,130,190)"), // Navy Blue
colord("rgb(210,210,100)"), // Lime Yellow
colord("rgb(190,100,130)"), // Maroon
colord("rgb(100,210,210)"), // Turquoise
colord("rgb(210,140,80)"), // Light Orange
colord("rgb(150,110,190)"), // Lavender
colord("rgb(180,210,120)"), // Light Green
colord("rgb(210,100,160)"), // Hot Pink
colord("rgb(100,140,110)"), // Sea Green
colord("rgb(230,180,180)"), // Light Pink
colord("rgb(120,120,190)"), // Periwinkle
colord("rgb(190,170,100)"), // Sand
colord("rgb(100,180,160)"), // Aquamarine
colord("rgb(210,160,200)"), // Orchid
colord("rgb(170,190,100)"), // Yellow Green
colord("rgb(100,130,150)"), // Steel Blue
colord("rgb(230,140,140)"), // Salmon
colord("rgb(140,180,220)"), // Light Blue
colord("rgb(200,160,110)"), // Tan
colord("rgb(180,130,180)"), // Plum
colord("rgb(130,200,130)"), // Light Sea Green
colord("rgb(220,120,120)"), // Coral
colord("rgb(120,160,200)"), // Cornflower Blue
colord("rgb(200,200,140)"), // Khaki
colord("rgb(160,120,160)"), // Purple Gray
colord("rgb(140,180,140)"), // Dark Sea Green
colord("rgb(200,130,110)"), // Dark Salmon
colord("rgb(130,170,190)"), // Cadet Blue
colord("rgb(190,180,160)"), // Tan Gray
colord("rgb(170,140,190)"), // Medium Purple
colord("rgb(160,190,160)"), // Pale Green
colord("rgb(190,150,130)"), // Rosy Brown
colord("rgb(140,150,180)"), // Light Slate Gray
colord("rgb(180,170,140)"), // Dark Khaki
colord("rgb(150,130,150)"), // Thistle
colord("rgb(170,190,180)"), // Pale Blue Green
colord("rgb(190,140,150)"), // Puce
colord("rgb(130,180,170)"), // Medium Aquamarine
colord("rgb(180,160,180)"), // Mauve
colord("rgb(160,180,140)"), // Dark Olive Green
colord("rgb(170,150,170)"), // Dusty Rose
colord("rgb(100,180,230)"), // Sky Blue
colord("rgb(230,180,80)"), // Golden Yellow
colord("rgb(180,100,230)"), // Purple
colord("rgb(80,200,120)"), // Emerald Green
colord("rgb(230,130,180)"), // Pink
colord("rgb(100,160,80)"), // Olive Green
colord("rgb(230,150,100)"), // Peach
colord("rgb(80,130,190)"), // Navy Blue
colord("rgb(210,210,100)"), // Lime Yellow
colord("rgb(190,100,130)"), // Maroon
colord("rgb(100,210,210)"), // Turquoise
colord("rgb(210,140,80)"), // Light Orange
colord("rgb(150,110,190)"), // Lavender
colord("rgb(180,210,120)"), // Light Green
colord("rgb(210,100,160)"), // Hot Pink
colord("rgb(100,140,110)"), // Sea Green
colord("rgb(230,180,180)"), // Light Pink
colord("rgb(120,120,190)"), // Periwinkle
colord("rgb(190,170,100)"), // Sand
colord("rgb(100,180,160)"), // Aquamarine
colord("rgb(210,160,200)"), // Orchid
colord("rgb(170,190,100)"), // Yellow Green
colord("rgb(100,130,150)"), // Steel Blue
colord("rgb(230,140,140)"), // Salmon
colord("rgb(140,180,220)"), // Light Blue
colord("rgb(200,160,110)"), // Tan
colord("rgb(180,130,180)"), // Plum
colord("rgb(130,200,130)"), // Light Sea Green
colord("rgb(220,120,120)"), // Coral
colord("rgb(120,160,200)"), // Cornflower Blue
colord("rgb(200,200,140)"), // Khaki
colord("rgb(160,120,160)"), // Purple Gray
colord("rgb(140,180,140)"), // Dark Sea Green
colord("rgb(200,130,110)"), // Dark Salmon
colord("rgb(130,170,190)"), // Cadet Blue
colord("rgb(190,180,160)"), // Tan Gray
colord("rgb(170,140,190)"), // Medium Purple
colord("rgb(160,190,160)"), // Pale Green
colord("rgb(190,150,130)"), // Rosy Brown
colord("rgb(140,150,180)"), // Light Slate Gray
colord("rgb(190,180,160)"), // Tan Gray
colord("rgb(180,170,140)"), // Dark Khaki
colord("rgb(150,130,150)"), // Thistle
colord("rgb(170,190,180)"), // Pale Blue Green
colord("rgb(190,140,150)"), // Puce
colord("rgb(130,180,170)"), // Medium Aquamarine
colord("rgb(180,160,180)"), // Mauve
colord("rgb(160,180,140)"), // Dark Olive Green
colord("rgb(170,150,170)"), // Dusty Rose
colord("rgb(200,200,140)"), // Khaki
colord("rgb(190,170,100)"), // Sand
];
// Bright pastel theme with 64 colors
export const humanColors: Colord[] = [
colord("rgb(16,185,129)"), // Sea Green
colord("rgb(34,197,94)"), // Emerald
colord("rgb(45,212,191)"), // Turquoise
colord("rgb(48,178,180)"), // Teal
colord("rgb(52,211,153)"), // Spearmint
colord("rgb(56,189,248)"), // Light Blue
colord("rgb(59,130,246)"), // Royal Blue
colord("rgb(67,190,84)"), // Fresh Green
colord("rgb(74,222,128)"), // Mint
colord("rgb(79,70,229)"), // Indigo
colord("rgb(82,183,136)"), // Jade
colord("rgb(96,165,250)"), // Sky Blue
colord("rgb(99,202,253)"), // Azure
colord("rgb(110,231,183)"), // Seafoam
colord("rgb(124,58,237)"), // Royal Purple
colord("rgb(125,211,252)"), // Crystal Blue
colord("rgb(132,204,22)"), // Lime
colord("rgb(133,77,14)"), // Chocolate
colord("rgb(134,239,172)"), // Light Green
colord("rgb(147,51,234)"), // Bright Purple
colord("rgb(147,197,253)"), // Powder Blue
colord("rgb(151,255,187)"), // Fresh Mint
colord("rgb(163,230,53)"), // Yellow Green
colord("rgb(167,139,250)"), // Periwinkle
colord("rgb(168,85,247)"), // Vibrant Purple
colord("rgb(179,136,255)"), // Light Purple
colord("rgb(132,204,22)"), // Lime
colord("rgb(16,185,129)"), // Sea Green
colord("rgb(52,211,153)"), // Spearmint
colord("rgb(45,212,191)"), // Turquoise
colord("rgb(74,222,128)"), // Mint
colord("rgb(110,231,183)"), // Seafoam
colord("rgb(134,239,172)"), // Light Green
colord("rgb(151,255,187)"), // Fresh Mint
colord("rgb(186,255,201)"), // Pale Emerald
colord("rgb(230,250,210)"), // Pastel Lime
colord("rgb(34,197,94)"), // Emerald
colord("rgb(67,190,84)"), // Fresh Green
colord("rgb(82,183,136)"), // Jade
colord("rgb(48,178,180)"), // Teal
colord("rgb(230,255,250)"), // Mint Whisper
colord("rgb(220,240,250)"), // Ice Blue
colord("rgb(233,213,255)"), // Light Lilac
colord("rgb(204,204,255)"), // Soft Lavender Blue
colord("rgb(220,220,255)"), // Meringue Blue
colord("rgb(202,225,255)"), // Baby Blue
colord("rgb(147,197,253)"), // Powder Blue
colord("rgb(125,211,252)"), // Crystal Blue
colord("rgb(99,202,253)"), // Azure
colord("rgb(56,189,248)"), // Light Blue
colord("rgb(96,165,250)"), // Sky Blue
colord("rgb(59,130,246)"), // Royal Blue
colord("rgb(79,70,229)"), // Indigo
colord("rgb(124,58,237)"), // Royal Purple
colord("rgb(147,51,234)"), // Bright Purple
colord("rgb(179,136,255)"), // Light Purple
colord("rgb(167,139,250)"), // Periwinkle
colord("rgb(217,70,239)"), // Fuchsia
colord("rgb(168,85,247)"), // Vibrant Purple
colord("rgb(190,92,251)"), // Amethyst
colord("rgb(192,132,252)"), // Lavender
colord("rgb(202,138,4)"), // Rich Gold
colord("rgb(202,225,255)"), // Baby Blue
colord("rgb(204,204,255)"), // Soft Lavender Blue
colord("rgb(217,70,239)"), // Fuchsia
colord("rgb(220,38,38)"), // Ruby
colord("rgb(220,220,255)"), // Meringue Blue
colord("rgb(220,240,250)"), // Ice Blue
colord("rgb(230,250,210)"), // Pastel Lime
colord("rgb(230,255,250)"), // Mint Whisper
colord("rgb(233,213,255)"), // Light Lilac
colord("rgb(234,88,12)"), // Burnt Orange
colord("rgb(234,179,8)"), // Sunflower
colord("rgb(235,75,75)"), // Bright Red
colord("rgb(236,72,153)"), // Deep Pink
colord("rgb(239,68,68)"), // Crimson
colord("rgb(240,171,252)"), // Orchid
colord("rgb(240,240,200)"), // Light Khaki
colord("rgb(244,114,182)"), // Rose
colord("rgb(236,72,153)"), // Deep Pink
colord("rgb(220,38,38)"), // Ruby
colord("rgb(239,68,68)"), // Crimson
colord("rgb(235,75,75)"), // Bright Red
colord("rgb(245,101,101)"), // Coral
colord("rgb(245,158,11)"), // Amber
colord("rgb(248,113,113)"), // Warm Red
colord("rgb(249,115,22)"), // Tangerine
colord("rgb(250,215,225)"), // Cotton Candy
colord("rgb(250,250,210)"), // Pastel Lemon
colord("rgb(251,113,133)"), // Watermelon
colord("rgb(251,146,60)"), // Light Orange
colord("rgb(251,191,36)"), // Marigold
colord("rgb(251,235,245)"), // Rose Powder
colord("rgb(252,165,165)"), // Peach
colord("rgb(252,211,77)"), // Golden
colord("rgb(253,164,175)"), // Salmon Pink
colord("rgb(252,165,165)"), // Peach
colord("rgb(255,204,229)"), // Blush Pink
colord("rgb(255,223,186)"), // Apricot Cream
colord("rgb(250,215,225)"), // Cotton Candy
colord("rgb(251,235,245)"), // Rose Powder
colord("rgb(240,240,200)"), // Light Khaki
colord("rgb(250,250,210)"), // Pastel Lemon
colord("rgb(255,240,200)"), // Vanilla
colord("rgb(255,223,186)"), // Apricot Cream
colord("rgb(252,211,77)"), // Golden
colord("rgb(251,191,36)"), // Marigold
colord("rgb(234,179,8)"), // Sunflower
colord("rgb(202,138,4)"), // Rich Gold
colord("rgb(245,158,11)"), // Amber
colord("rgb(251,146,60)"), // Light Orange
colord("rgb(249,115,22)"), // Tangerine
colord("rgb(234,88,12)"), // Burnt Orange
colord("rgb(133,77,14)"), // Chocolate
];
export const botColors: Colord[] = [
colord("rgb(190,120,120)"), // Muted Red
colord("rgb(120,160,190)"), // Muted Sky Blue
colord("rgb(190,160,100)"), // Muted Golden Yellow
colord("rgb(160,120,190)"), // Muted Purple
colord("rgb(100,170,130)"), // Muted Emerald Green
colord("rgb(190,130,160)"), // Muted Pink
colord("rgb(120,150,100)"), // Muted Olive Green
colord("rgb(190,140,120)"), // Muted Peach
colord("rgb(100,120,160)"), // Muted Navy Blue
colord("rgb(170,170,120)"), // Muted Lime Yellow
colord("rgb(160,120,130)"), // Muted Maroon
colord("rgb(120,170,170)"), // Muted Turquoise
colord("rgb(170,140,100)"), // Muted Light Orange
colord("rgb(140,120,160)"), // Muted Lavender
colord("rgb(150,170,130)"), // Muted Light Green
colord("rgb(170,120,140)"), // Muted Hot Pink
colord("rgb(120,140,120)"), // Muted Sea Green
colord("rgb(180,160,160)"), // Muted Light Pink
colord("rgb(130,130,160)"), // Muted Periwinkle
colord("rgb(160,150,120)"), // Muted Sand
colord("rgb(120,160,150)"), // Muted Aquamarine
colord("rgb(170,150,170)"), // Muted Orchid
colord("rgb(150,160,120)"), // Muted Yellow Green
colord("rgb(120,130,140)"), // Muted Steel Blue
colord("rgb(180,140,140)"), // Muted Salmon
colord("rgb(140,160,170)"), // Muted Light Blue
colord("rgb(170,150,130)"), // Muted Tan
colord("rgb(160,130,160)"), // Muted Plum
colord("rgb(130,170,130)"), // Muted Light Sea Green
colord("rgb(170,130,130)"), // Muted Coral
colord("rgb(130,150,170)"), // Muted Cornflower Blue
colord("rgb(170,170,140)"), // Muted Khaki
colord("rgb(150,130,150)"), // Muted Purple Gray
colord("rgb(140,160,140)"), // Muted Dark Sea Green
colord("rgb(170,130,120)"), // Muted Dark Salmon
colord("rgb(130,150,160)"), // Muted Cadet Blue
colord("rgb(160,160,150)"), // Muted Tan Gray
colord("rgb(150,140,160)"), // Muted Medium Purple
colord("rgb(150,170,150)"), // Muted Pale Green
colord("rgb(160,140,130)"), // Muted Rosy Brown
colord("rgb(140,150,160)"), // Muted Light Slate Gray
colord("rgb(160,150,140)"), // Muted Dark Khaki
colord("rgb(140,130,140)"), // Muted Thistle
colord("rgb(150,160,160)"), // Muted Pale Blue Green
colord("rgb(160,140,150)"), // Muted Puce
colord("rgb(130,160,150)"), // Muted Medium Aquamarine
colord("rgb(160,150,160)"), // Muted Mauve
colord("rgb(150,160,140)"), // Muted Dark Olive Green
colord("rgb(160,160,150)"), // Muted Tan Gray
colord("rgb(170,170,140)"), // Muted Khaki
colord("rgb(170,170,120)"), // Muted Lime Yellow
colord("rgb(150,160,120)"), // Muted Yellow Green
colord("rgb(150,170,130)"), // Muted Light Green
colord("rgb(150,170,150)"), // Muted Pale Green
colord("rgb(130,170,130)"), // Muted Light Sea Green
colord("rgb(140,160,140)"), // Muted Dark Sea Green
colord("rgb(120,150,100)"), // Muted Olive Green
colord("rgb(120,140,120)"), // Muted Sea Green
colord("rgb(100,170,130)"), // Muted Emerald Green
colord("rgb(120,160,150)"), // Muted Aquamarine
colord("rgb(130,160,150)"), // Muted Medium Aquamarine
colord("rgb(120,170,170)"), // Muted Turquoise
colord("rgb(120,160,190)"), // Muted Sky Blue
colord("rgb(130,150,170)"), // Muted Cornflower Blue
colord("rgb(130,150,160)"), // Muted Cadet Blue
colord("rgb(140,150,160)"), // Muted Light Slate Gray
colord("rgb(140,160,170)"), // Muted Light Blue
colord("rgb(150,160,160)"), // Muted Pale Blue Green
colord("rgb(100,120,160)"), // Muted Navy Blue
colord("rgb(120,130,140)"), // Muted Steel Blue
colord("rgb(130,130,160)"), // Muted Periwinkle
colord("rgb(140,130,140)"), // Muted Thistle
colord("rgb(140,120,160)"), // Muted Lavender
colord("rgb(150,130,150)"), // Muted Purple Gray
colord("rgb(150,140,160)"), // Muted Medium Purple
colord("rgb(160,130,160)"), // Muted Plum
colord("rgb(170,150,170)"), // Muted Orchid
colord("rgb(160,120,190)"), // Muted Purple
colord("rgb(160,120,130)"), // Muted Maroon
colord("rgb(170,120,140)"), // Muted Hot Pink
colord("rgb(170,130,120)"), // Muted Dark Salmon
colord("rgb(170,130,130)"), // Muted Coral
colord("rgb(180,140,140)"), // Muted Salmon
colord("rgb(190,130,160)"), // Muted Pink
colord("rgb(190,120,120)"), // Muted Red
colord("rgb(190,140,120)"), // Muted Peach
colord("rgb(190,160,100)"), // Muted Golden Yellow
colord("rgb(170,140,100)"), // Muted Light Orange
colord("rgb(160,140,130)"), // Muted Rosy Brown
colord("rgb(170,150,130)"), // Muted Tan
colord("rgb(160,150,120)"), // Muted Sand
colord("rgb(160,150,140)"), // Muted Dark Khaki
colord("rgb(160,140,150)"), // Muted Puce
colord("rgb(160,150,160)"), // Muted Mauve
colord("rgb(150,140,150)"), // Muted Dusty Rose
colord("rgb(180,160,160)"), // Muted Light Pink
];
// Fallback colors for when the color palette is exhausted.
+2
View File
@@ -190,6 +190,8 @@ export interface Theme {
// Don't call directly, use PlayerView
territoryColor(playerInfo: PlayerView): Colord;
// Don't call directly, use PlayerView
structureColors(territoryColor: Colord): { light: Colord; dark: Colord };
// Don't call directly, use PlayerView
borderColor(territoryColor: Colord): Colord;
// Don't call directly, use PlayerView
defendedBorderColors(territoryColor: Colord): { light: Colord; dark: Colord };
+2 -1
View File
@@ -60,6 +60,7 @@ const numPlayersConfig = {
[GameMapType.Europe]: [100, 70, 50],
[GameMapType.EuropeClassic]: [50, 30, 30],
[GameMapType.FalklandIslands]: [50, 30, 20],
[GameMapType.FourIslands]: [20, 15, 10],
[GameMapType.FaroeIslands]: [20, 15, 10],
[GameMapType.GatewayToTheAtlantic]: [100, 70, 50],
[GameMapType.GiantWorldMap]: [100, 70, 50],
@@ -692,7 +693,7 @@ export class DefaultConfig implements Config {
if (attacker.isPlayer() && defender.isPlayer()) {
if (defender.isDisconnected() && attacker.isOnSameTeam(defender)) {
// No troop loss if defender is disconnected.
// No troop loss if defender is disconnected and on same team
mag = 0;
}
if (
+54 -4
View File
@@ -1,4 +1,4 @@
import { Colord, colord } from "colord";
import { Colord, colord, LabaColor } from "colord";
import { PseudoRandom } from "../PseudoRandom";
import { PlayerType, Team, TerrainType } from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
@@ -7,10 +7,7 @@ import { ColorAllocator } from "./ColorAllocator";
import { botColors, fallbackColors, humanColors, nationColors } from "./Colors";
import { Theme } from "./Config";
type ColorCache = Map<string, Colord>;
export class PastelTheme implements Theme {
private borderColorCache: ColorCache = new Map<string, Colord>();
private rand = new PseudoRandom(123);
private humanColorAllocator = new ColorAllocator(humanColors, fallbackColors);
private botColorAllocator = new ColorAllocator(botColors, botColors);
@@ -65,6 +62,59 @@ export class PastelTheme implements Theme {
return this.nationColorAllocator.assignColor(player.id());
}
structureColors(territoryColor: Colord): { light: Colord; dark: Colord } {
// Convert territory color to LAB color space. Territory color is rendered in game with alpha = 150/255, use that here.
const lightLAB = territoryColor.alpha(150 / 255).toLab();
// Get "border color" from territory color & convert to LAB color space
const darkLAB = this.borderColor(territoryColor).toLab();
// Calculate the contrast of the two provided colors
let contrast = this.contrast(lightLAB, darkLAB);
// Don't want excessive contrast, so incrementally increase contrast within a loop.
// Define target values, looping limits, and loop counter
const loopLimit = 10; // Switch from darkening border to lightening fill if loopLimit is reached
const maxIterations = 50; // maximum number of loops allowed, throw error above this limit
const contrastTarget = 0.5;
let loopCount = 0;
// Adjust luminance by 5 in each iteration. This is a balance between speed and not overdoing contrast changes.
const luminanceChange = 5;
while (contrast < contrastTarget) {
if (loopCount > maxIterations) {
// Prevent runaway loops
console.warn(`Infinite loop detected during structure color calculation.
Light color: ${colord(lightLAB).toRgbString()},
Dark color: ${colord(darkLAB).toRgbString()},
Contrast: ${contrast}`);
break;
// Increase the light color if the "loop limit" has been reach
// (probably due to the dark color already being as dark as it can be)
} else if (loopCount > loopLimit) {
lightLAB.l = this.clamp(lightLAB.l + luminanceChange);
// Decrease the dark color first to keep the light color as close
// to the territory color as possible
} else {
darkLAB.l = this.clamp(darkLAB.l - luminanceChange);
}
// re-calculate contrast and increment loop counter
contrast = this.contrast(lightLAB, darkLAB);
loopCount++;
}
return { light: colord(lightLAB), dark: colord(darkLAB) };
}
private contrast(first: LabaColor, second: LabaColor): number {
return colord(first).delta(colord(second));
}
private clamp(num: number, low: number = 0, high: number = 100): number {
return Math.min(Math.max(low, num), high);
}
// Don't call directly, use PlayerView
borderColor(territoryColor: Colord): Colord {
return territoryColor.darken(0.125);
+7
View File
@@ -186,6 +186,13 @@ export class AttackExecution implements Execution {
this._owner.id(),
);
}
if (this.removeTroops === false && this.sourceTile === null) {
// startTroops are always added to attack troops at init but not always removed from owner troops
// subtract startTroops from attack troops so we don't give back startTroops to owner that were never removed
// boat attacks (sourceTile !== null) are the exception: troops were removed at departure and must be returned after attack still
this.attack.setTroops(this.attack.troops() - (this.startTroops ?? 0));
}
const survivors = this.attack.troops() - deaths;
this._owner.addTroops(survivors);
this.attack.delete();
+55 -7
View File
@@ -1,3 +1,4 @@
import { renderTroops } from "../../client/Utils";
import {
Execution,
Game,
@@ -14,6 +15,7 @@ import { PathFindResultType } from "../pathfinding/AStar";
import { PathFinder } from "../pathfinding/PathFinding";
import { AttackExecution } from "./AttackExecution";
const malusForRetreat = 25;
export class TransportShipExecution implements Execution {
private lastMove: number;
@@ -33,13 +35,17 @@ export class TransportShipExecution implements Execution {
private pathFinder: PathFinder;
private originalOwner: Player;
constructor(
private attacker: Player,
private targetID: PlayerID | null,
private ref: TileRef,
private startTroops: number,
private src: TileRef | null,
) {}
) {
this.originalOwner = this.attacker;
}
activeDuringSpawnPhase(): boolean {
return false;
@@ -173,11 +179,44 @@ export class TransportShipExecution implements Execution {
}
this.lastMove = ticks;
if (this.boat.retreating()) {
this.dst = this.src!; // src is guaranteed to be set at this point
// Team mate can conquer disconnected player and get their ships
// captureUnit has changed the owner of the unit, now update attacker
const boatOwner = this.boat.owner();
if (
this.originalOwner.isDisconnected() &&
boatOwner !== this.originalOwner &&
boatOwner.isOnSameTeam(this.originalOwner)
) {
this.attacker = boatOwner;
this.originalOwner = boatOwner; // for when this owner disconnects too
}
if (this.boat.targetTile() !== this.dst) {
this.boat.setTargetTile(this.dst);
if (this.boat.retreating()) {
// Ensure retreat source is still valid for (new) owner
if (this.mg.owner(this.src!) !== this.attacker) {
// Use bestTransportShipSpawn, not canBuild because of its max boats check etc
const newSrc = this.attacker.bestTransportShipSpawn(this.dst);
if (newSrc === false) {
this.src = null;
} else {
this.src = newSrc;
}
}
if (this.src === null) {
console.warn(
`TransportShipExecution: retreating but no src found for new attacker`,
);
this.attacker.addTroops(this.boat.troops());
this.boat.delete(false);
this.active = false;
return;
} else {
this.dst = this.src;
if (this.boat.targetTile() !== this.dst) {
this.boat.setTargetTile(this.dst);
}
}
}
@@ -190,14 +229,23 @@ export class TransportShipExecution implements Execution {
switch (result.type) {
case PathFindResultType.Completed:
if (this.mg.owner(this.dst) === this.attacker) {
this.attacker.addTroops(this.boat.troops());
const deaths = this.boat.troops() * (malusForRetreat / 100);
const survivors = this.boat.troops() - deaths;
this.attacker.addTroops(survivors);
this.boat.delete(false);
this.active = false;
// Record stats
this.mg
.stats()
.boatArriveTroops(this.attacker, this.target, this.boat.troops());
.boatArriveTroops(this.attacker, this.target, survivors);
if (deaths) {
this.mg.displayMessage(
`Attack cancelled, ${renderTroops(deaths)} soldiers killed during retreat.`,
MessageType.ATTACK_CANCELLED,
this.attacker.id(),
);
}
return;
}
this.attacker.conquer(this.dst);
+1 -5
View File
@@ -55,10 +55,6 @@ export class WarshipExecution implements Execution {
this.warship.delete();
return;
}
if (this.warship.owner().isDisconnected()) {
this.warship.delete();
return;
}
const hasPort = this.warship.owner().unitCount(UnitType.Port) > 0;
if (hasPort) {
@@ -93,7 +89,7 @@ export class WarshipExecution implements Execution {
if (
unit.owner() === this.warship.owner() ||
unit === this.warship ||
unit.owner().isFriendly(this.warship.owner()) ||
unit.owner().isFriendly(this.warship.owner(), true) ||
this.alreadySentShell.has(unit)
) {
continue;
+3 -1
View File
@@ -101,6 +101,7 @@ export enum GameMapType {
Montreal = "Montreal",
Achiran = "Achiran",
BaikalNukeWars = "Baikal (Nuke Wars)",
FourIslands = "Four Islands",
}
export type GameMapName = keyof typeof GameMapType;
@@ -143,6 +144,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.DeglaciatedAntarctica,
GameMapType.Achiran,
GameMapType.BaikalNukeWars,
GameMapType.FourIslands,
],
};
@@ -601,7 +603,7 @@ export interface Player {
decayRelations(): void;
isOnSameTeam(other: Player): boolean;
// Either allied or on same team.
isFriendly(other: Player): boolean;
isFriendly(other: Player, treatAFKFriendly?: boolean): boolean;
team(): Team | null;
clan(): string | null;
incomingAllianceRequests(): AllianceRequest[];
+14
View File
@@ -895,6 +895,20 @@ export class GameImpl implements Game {
return this._railNetwork;
}
conquerPlayer(conqueror: Player, conquered: Player) {
if (conquered.isDisconnected() && conqueror.isOnSameTeam(conquered)) {
const ships = conquered
.units()
.filter(
(u) =>
u.type() === UnitType.Warship ||
u.type() === UnitType.TransportShip,
);
for (const ship of ships) {
conqueror.captureUnit(ship);
}
}
const gold = conquered.gold();
this.displayMessage(
`Conquered ${conquered.displayName()} received ${renderNumber(
+1
View File
@@ -181,6 +181,7 @@ export interface AllianceView {
other: PlayerID;
createdAt: Tick;
expiresAt: Tick;
hasExtensionRequest: boolean;
}
export interface AllianceRequestUpdate {
+11
View File
@@ -190,6 +190,8 @@ export class PlayerView {
private _territoryColor: Colord;
private _borderColor: Colord;
// Update here to include structure light and dark colors
private _structureColors: { light: Colord; dark: Colord };
private _defendedBorderColors: { light: Colord; dark: Colord };
constructor(
@@ -235,6 +237,11 @@ export class PlayerView {
this._territoryColor = defaultTerritoryColor;
}
this._structureColors = this.game
.config()
.theme()
.structureColors(this._territoryColor);
const maybeFocusedBorderColor =
this.game.myClientID() === this.data.clientID
? this.game.config().theme().focusedBorderColor()
@@ -268,6 +275,10 @@ export class PlayerView {
return isPrimary ? this._territoryColor : this._borderColor;
}
structureColors(): { light: Colord; dark: Colord } {
return this._structureColors;
}
borderColor(tile?: TileRef, isDefended: boolean = false): Colord {
if (tile === undefined || !isDefended) {
return this._borderColor;
+6 -2
View File
@@ -173,6 +173,10 @@ export class PlayerImpl implements Player {
other: a.other(this).id(),
createdAt: a.createdAt(),
expiresAt: a.expiresAt(),
hasExtensionRequest:
a.expiresAt() <=
this.mg.ticks() +
this.mg.config().allianceExtensionPromptOffset(),
}) satisfies AllianceView,
),
hasSpawned: this.hasSpawned(),
@@ -791,8 +795,8 @@ export class PlayerImpl implements Player {
return this._team === other.team();
}
isFriendly(other: Player): boolean {
if (other.isDisconnected()) {
isFriendly(other: Player, treatAFKFriendly: boolean = false): boolean {
if (other.isDisconnected() && !treatAFKFriendly) {
return false;
}
return this.isOnSameTeam(other) || this.isAlliedWith(other);
+2
View File
@@ -148,6 +148,8 @@ export function bestShoreDeploymentSource(
if (t === null) return false;
const candidates = candidateShoreTiles(gm, player, t);
if (candidates.length === 0) return false;
const aStar = new MiniAStar(gm, gm.miniMap(), candidates, t, 1_000_000, 1);
const result = aStar.compute();
if (result !== PathFindResultType.Completed) {