Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added resources/images/test/test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/client/ClientGameRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ export interface LobbyConfig {
serverConfig: ServerConfig;
pattern: PlayerPattern | undefined;
flag: string;

structurePort: string | undefined;
structureCity: string | undefined;
structureFactory: string | undefined;
structureMissilesilo: string | undefined;
structureDefensepost: string | undefined;
structureSamlauncher: string | undefined;

spriteTransportship: string | undefined;
spriteWarship: string | undefined;
spriteSammissile: string | undefined;
spriteAtombomb: string | undefined;
spriteHydrogenbomb: string | undefined;
spriteTradeship: string | undefined;
spriteMirv: string | undefined;
spriteEngine: string | undefined;
spriteCarriage: string | undefined;
spriteLoadedcarriage: string | undefined;

playerName: string;
clientID: ClientID;
gameID: GameID;
Expand Down
22 changes: 22 additions & 0 deletions src/client/CosmeticPackLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export function fetchUrl(
packId: string | undefined,
type: string,
): string | undefined {
// TODO: Fetches the resource URL from the API server.

// Request parameters:
// - packKey: identifier of the cosmetic pack
// - type: asset type (e.g., "structurePort", "structureCity")
// Response:
// - URL string pointing to the requested asset

// Even if this approach changes, this function will be responsible for obtaining the URL by some method.

switch (packId) {
case "base":
return;
case "test":
return "/images/test/test.png"; // Example URL for testing
}
return;
}
31 changes: 31 additions & 0 deletions src/client/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,37 @@ class Client {
this.flagInput === null || this.flagInput.getCurrentFlag() === "xx"
? ""
: this.flagInput.getCurrentFlag(),
structurePort:
this.userSettings.getSelectedStructurePort() ?? undefined,
structureCity:
this.userSettings.getSelectedStructureCity() ?? undefined,
structureFactory:
this.userSettings.getSelectedStructureFactory() ?? undefined,
structureMissilesilo:
this.userSettings.getSelectedStructureMissilesilo() ?? undefined,
structureDefensepost:
this.userSettings.getSelectedStructureDefensepost() ?? undefined,
structureSamlauncher:
this.userSettings.getSelectedStructureSamlauncher() ?? undefined,

spriteTransportship:
this.userSettings.getSelectedSpriteTransportship() ?? undefined,
spriteWarship:
this.userSettings.getSelectedSpriteWarship() ?? undefined,
spriteSammissile:
this.userSettings.getSelectedSpriteSammissile() ?? undefined,
spriteAtombomb:
this.userSettings.getSelectedSpriteAtombomb() ?? undefined,
spriteHydrogenbomb:
this.userSettings.getSelectedSpriteHydrogenbomb() ?? undefined,
spriteTradeship:
this.userSettings.getSelectedSpriteTradeship() ?? undefined,
spriteMirv: this.userSettings.getSelectedSpriteMirv() ?? undefined,
spriteEngine: this.userSettings.getSelectedSpriteEngine() ?? undefined,
spriteCarriage:
this.userSettings.getSelectedSpriteCarriage() ?? undefined,
spriteLoadedcarriage:
this.userSettings.getSelectedSpriteLoadedcarriage() ?? undefined,
playerName: this.usernameInput?.getCurrentUsername() ?? "",
token: getPlayToken(),
clientID: lobby.clientID,
Expand Down
79 changes: 79 additions & 0 deletions src/client/SinglePlayerModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./components/Difficulties";
import "./components/Maps";
import { fetchUrl } from "./CosmeticPackLoader";
import { fetchCosmetics } from "./Cosmetics";
import { FlagInput } from "./FlagInput";
import { JoinLobbyEvent } from "./Main";
Expand Down Expand Up @@ -466,6 +467,84 @@ export class SinglePlayerModal extends LitElement {
? ""
: flagInput.getCurrentFlag(),
pattern: selectedPattern ?? undefined,
pack: {
structurePort: fetchUrl(
this.userSettings.getSelectedStructurePort() ?? undefined,
"structurePort",
),
structureCity: fetchUrl(
this.userSettings.getSelectedStructureCity() ?? undefined,
"structureCity",
),
structureFactory: fetchUrl(
this.userSettings.getSelectedStructureFactory() ??
undefined,
"structureFactory",
),
structureMissilesilo: fetchUrl(
this.userSettings.getSelectedStructureMissilesilo() ??
undefined,
"structureMissilesilo",
),
structureDefensepost: fetchUrl(
this.userSettings.getSelectedStructureDefensepost() ??
undefined,
"structureDefensepost",
),
structureSamlauncher: fetchUrl(
this.userSettings.getSelectedStructureSamlauncher() ??
undefined,
"structureSamlauncher",
),

spriteTransportship: fetchUrl(
this.userSettings.getSelectedSpriteTransportship() ??
undefined,
"spriteTransportship",
),
spriteWarship: fetchUrl(
this.userSettings.getSelectedSpriteWarship() ?? undefined,
"spriteWarship",
),
spriteSammissile: fetchUrl(
this.userSettings.getSelectedSpriteSammissile() ??
undefined,
"spriteSammissile",
),
spriteAtombomb: fetchUrl(
this.userSettings.getSelectedSpriteAtombomb() ??
undefined,
"spriteAtombomb",
),
spriteHydrogenbomb: fetchUrl(
this.userSettings.getSelectedSpriteHydrogenbomb() ??
undefined,
"spriteHydrogenbomb",
),
spriteTradeship: fetchUrl(
this.userSettings.getSelectedSpriteTradeship() ??
undefined,
"spriteTradeship",
),
spriteMirv: fetchUrl(
this.userSettings.getSelectedSpriteMirv() ?? undefined,
"spriteMirv",
),
spriteEngine: fetchUrl(
this.userSettings.getSelectedSpriteEngine() ?? undefined,
"spriteEngine",
),
spriteCarriage: fetchUrl(
this.userSettings.getSelectedSpriteCarriage() ??
undefined,
"spriteCarriage",
),
spriteLoadedcarriage: fetchUrl(
this.userSettings.getSelectedSpriteLoadedcarriage() ??
undefined,
"spriteLoadedcarriage",
),
},
},
},
],
Expand Down
18 changes: 18 additions & 0 deletions src/client/Transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,24 @@ export class Transport {
flag: this.lobbyConfig.flag,
patternName: this.lobbyConfig.pattern?.name,
patternColorPaletteName: this.lobbyConfig.pattern?.colorPalette?.name,

structurePort: this.lobbyConfig.structurePort,
structureCity: this.lobbyConfig.structureCity,
structureFactory: this.lobbyConfig.structureFactory,
structureMissilesilo: this.lobbyConfig.structureMissilesilo,
structureDefensepost: this.lobbyConfig.structureDefensepost,
structureSamlauncher: this.lobbyConfig.structureSamlauncher,

spriteTransportship: this.lobbyConfig.spriteTransportship,
spriteWarship: this.lobbyConfig.spriteWarship,
spriteSammissile: this.lobbyConfig.spriteSammissile,
spriteAtombomb: this.lobbyConfig.spriteAtombomb,
spriteHydrogenbomb: this.lobbyConfig.spriteHydrogenbomb,
spriteTradeship: this.lobbyConfig.spriteTradeship,
spriteMirv: this.lobbyConfig.spriteMirv,
spriteEngine: this.lobbyConfig.spriteEngine,
spriteCarriage: this.lobbyConfig.spriteCarriage,
spriteLoadedcarriage: this.lobbyConfig.spriteLoadedcarriage,
},
} satisfies ClientJoinMessage);
}
Expand Down
50 changes: 35 additions & 15 deletions src/client/graphics/SpriteLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import warshipSprite from "../../../resources/sprites/warship.png";
import { Theme } from "../../core/configuration/Config";
import { TrainType, UnitType } from "../../core/game/Game";
import { UnitView } from "../../core/game/GameView";
import { PlayerPack } from "../../core/Schemas";

// Can't reuse TrainType because "loaded" is not a type, just an attribute
const TrainTypeSprite = {
Expand All @@ -22,36 +23,55 @@ const TrainTypeSprite = {

type TrainTypeSprite = (typeof TrainTypeSprite)[keyof typeof TrainTypeSprite];

const SPRITE_CONFIG: Partial<Record<UnitType | TrainTypeSprite, string>> = {
[UnitType.TransportShip]: transportShipSprite,
[UnitType.Warship]: warshipSprite,
[UnitType.SAMMissile]: samMissileSprite,
[UnitType.AtomBomb]: atomBombSprite,
[UnitType.HydrogenBomb]: hydrogenBombSprite,
[UnitType.TradeShip]: tradeShipSprite,
[UnitType.MIRV]: mirvSprite,
[TrainTypeSprite.Engine]: trainEngineSprite,
[TrainTypeSprite.Carriage]: trainCarriageSprite,
[TrainTypeSprite.LoadedCarriage]: trainLoadedCarriageSprite,
const SPRITE_CONFIG: Partial<
Record<UnitType | TrainTypeSprite, { key: string; url: string }>
> = {
[UnitType.TransportShip]: {
key: "spriteTransportship",
url: transportShipSprite,
},
[UnitType.Warship]: { key: "spriteWarship", url: warshipSprite },
[UnitType.SAMMissile]: { key: "spriteSammissile", url: samMissileSprite },
[UnitType.AtomBomb]: { key: "spriteAtombomb", url: atomBombSprite },
[UnitType.HydrogenBomb]: {
key: "spriteHydrogenbomb",
url: hydrogenBombSprite,
},
[UnitType.TradeShip]: { key: "spriteTradeship", url: tradeShipSprite },
[UnitType.MIRV]: { key: "spriteMirv", url: mirvSprite },
[TrainTypeSprite.Engine]: { key: "spriteEngine", url: trainEngineSprite },
[TrainTypeSprite.Carriage]: {
key: "spriteCarriage",
url: trainCarriageSprite,
},
[TrainTypeSprite.LoadedCarriage]: {
key: "spriteLoadedcarriage",
url: trainLoadedCarriageSprite,
},
};

const spriteMap: Map<UnitType | TrainTypeSprite, ImageBitmap> = new Map();

// preload all images
export const loadAllSprites = async (): Promise<void> => {
export const loadAllSprites = async (pack: PlayerPack): Promise<void> => {
const entries = Object.entries(SPRITE_CONFIG);
const totalSprites = entries.length;
let loadedCount = 0;

await Promise.all(
entries.map(async ([unitType, url]) => {
entries.map(async ([unitType, value]) => {
const typedUnitType = unitType as UnitType | TrainTypeSprite;

if (!url || url === "") {
console.warn(`No sprite URL for ${typedUnitType}, skipping...`);
const key = value?.key;
const fallbackUrl = value?.url;

if (!fallbackUrl) {
console.warn(`No sprite url for ${typedUnitType}, skipping...`);
return;
}

const url = pack?.[key] ?? fallbackUrl;

try {
const img = new Image();
img.crossOrigin = "anonymous";
Expand Down
33 changes: 26 additions & 7 deletions src/client/graphics/layers/StructureLayer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { colord, Colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";

import cityIcon from "../../../../resources/images/buildings/cityAlt1.png";
import factoryIcon from "../../../../resources/images/buildings/factoryAlt1.png";
import shieldIcon from "../../../../resources/images/buildings/fortAlt3.png";
import anchorIcon from "../../../../resources/images/buildings/port1.png";
import missileSiloIcon from "../../../../resources/images/buildings/silo1.png";
import SAMMissileIcon from "../../../../resources/images/buildings/silo4.png";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { Cell, UnitType } from "../../../core/game/Game";
import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { PlayerPack } from "../../../core/Schemas";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";

const underConstructionColor = colord({ r: 150, g: 150, b: 150 });

Expand All @@ -25,6 +25,7 @@ const ZOOM_THRESHOLD = 4.3; // below this zoom level, structures are not rendere

interface UnitRenderConfig {
icon: string;
key: string;
borderRadius: number;
territoryRadius: number;
}
Expand All @@ -34,38 +35,46 @@ export class StructureLayer implements Layer {
private context: CanvasRenderingContext2D;
private unitIcons: Map<string, HTMLImageElement> = new Map();
private theme: Theme;
private pack: PlayerPack;
private structureLoaded = false;
private tempCanvas: HTMLCanvasElement;
private tempContext: CanvasRenderingContext2D;

// Configuration for supported unit types only
private readonly unitConfigs: Partial<Record<UnitType, UnitRenderConfig>> = {
private unitConfigs: Partial<Record<UnitType, UnitRenderConfig>> = {
[UnitType.Port]: {
icon: anchorIcon,
key: "structurePort",
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.City]: {
icon: cityIcon,
key: "structureCity",
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.Factory]: {
icon: factoryIcon,
key: "structureFactory",
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.MissileSilo]: {
icon: missileSiloIcon,
key: "structureMissilesilo",
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.DefensePost]: {
icon: shieldIcon,
key: "structureDefensepost",
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.SAMLauncher]: {
icon: SAMMissileIcon,
key: "structureSamlauncher",
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
Expand All @@ -86,6 +95,7 @@ export class StructureLayer implements Layer {

private loadIcon(unitType: string, config: UnitRenderConfig) {
const image = new Image();
console.log(`loading icon for ${unitType} from ${config.icon}`);
image.src = config.icon;
image.onload = () => {
this.unitIcons.set(unitType, image);
Expand All @@ -98,8 +108,9 @@ export class StructureLayer implements Layer {
};
}

private loadIconData() {
private async loadIconData() {
Object.entries(this.unitConfigs).forEach(([unitType, config]) => {
config.icon = this.pack?.[config.key] ?? config.icon;
this.loadIcon(unitType, config);
});
}
Expand All @@ -116,6 +127,14 @@ export class StructureLayer implements Layer {
if (unit === undefined) continue;
this.handleUnitRendering(unit);
}
if (!this.structureLoaded) {
const myPlayer = this.game.myPlayer();
if (myPlayer) {
this.pack = myPlayer.cosmetics.pack ?? {};
this.loadIconData();
this.structureLoaded = true;
}
}
}

init() {
Expand Down
Loading
Loading