This commit is contained in:
Aotumuri
2025-04-26 13:50:02 +09:00
parent 1f890396ab
commit fab63c73bf
20 changed files with 128 additions and 8 deletions
+45
View File
@@ -0,0 +1,45 @@
{
"patterns": {
"stripes": {
"tileWidth": 2,
"tileHeight": 2,
"scale": 2,
"pattern": [
[1, 0],
[1, 0]
]
},
"checkerboard": {
"tileWidth": 2,
"tileHeight": 2,
"scale": 2,
"pattern": [
[1, 0],
[0, 1]
]
},
"diagonal": {
"tileWidth": 16,
"tileHeight": 16,
"scale": 1,
"pattern": [
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
]
}
}
}
+1
View File
@@ -39,6 +39,7 @@ import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
export interface LobbyConfig {
serverConfig: ServerConfig;
pattern: string;
flag: string;
playerName: string;
clientID: ClientID;
+1
View File
@@ -208,6 +208,7 @@ class Client {
{
gameID: lobby.gameID,
serverConfig: config,
pattern: localStorage.getItem("territoryPattern"),
flag:
this.flagInput.getCurrentFlag() == "xx"
? ""
+1
View File
@@ -404,6 +404,7 @@ export class SinglePlayerModal extends LitElement {
flagInput.getCurrentFlag() == "xx"
? ""
: flagInput.getCurrentFlag(),
pattern: localStorage.getItem("territoryPattern"),
},
],
config: {
+2
View File
@@ -339,6 +339,7 @@ export class Transport {
persistentID: this.lobbyConfig.persistentID,
username: this.lobbyConfig.playerName,
flag: this.lobbyConfig.flag,
pattern: localStorage.getItem("territoryPattern"),
}),
),
);
@@ -393,6 +394,7 @@ export class Transport {
type: "spawn",
clientID: this.lobbyConfig.clientID,
flag: this.lobbyConfig.flag,
pattern: this.lobbyConfig.pattern,
name: this.lobbyConfig.playerName,
playerType: PlayerType.Human,
x: event.cell.x,
+26 -6
View File
@@ -1,5 +1,6 @@
import { PriorityQueue } from "@datastructures-js/priority-queue";
import { Colord } from "colord";
import territory_patterns from "../../../../resources/territory_patterns.json";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { Cell, PlayerType, UnitType } from "../../../core/game/Game";
@@ -280,12 +281,31 @@ export class TerritoryLayer implements Layer {
);
}
} else {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
this.theme.territoryColor(owner),
150,
);
const patternName = owner.pattern();
if (!patternName) {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
this.theme.territoryColor(owner),
150,
);
} else {
const x = this.game.x(tile);
const y = this.game.y(tile);
const baseColor = this.theme.territoryColor(owner);
const patternConfig = territory_patterns.patterns[patternName];
const { tileWidth, tileHeight, scale, pattern } = patternConfig;
const px = Math.floor(x / scale) % tileWidth;
const py = Math.floor(y / scale) % tileHeight;
const patternValue = pattern[py][px];
const colorToUse =
patternValue === 1 ? baseColor.darken(0.2) : baseColor;
this.paintCell(x, y, colorToUse, 150);
}
}
}
+1
View File
@@ -38,6 +38,7 @@ export async function createGameRunner(
gameStart.players.map(
(p) =>
new PlayerInfo(
p.pattern,
p.flag,
p.clientID == clientID
? sanitize(p.username)
+3
View File
@@ -185,6 +185,7 @@ export const AttackIntentSchema = BaseIntentSchema.extend({
export const SpawnIntentSchema = BaseIntentSchema.extend({
flag: z.string().nullable(),
pattern: z.string().nullable(),
type: z.literal("spawn"),
name: SafeString,
playerType: PlayerTypeSchema,
@@ -321,6 +322,7 @@ export const PlayerSchema = z.object({
clientID: ID,
username: SafeString,
flag: SafeString.optional(),
pattern: SafeString.optional(),
});
export const GameStartInfoSchema = z.object({
@@ -397,6 +399,7 @@ export const ClientJoinMessageSchema = ClientBaseMessageSchema.extend({
lastTurn: z.number(), // The last turn the client saw.
username: SafeString,
flag: SafeString.nullable().optional(),
pattern: SafeString.nullable().optional(),
});
export const ClientMessageSchema = z.union([
+8 -1
View File
@@ -47,7 +47,14 @@ export class BotSpawner {
}
}
return new SpawnExecution(
new PlayerInfo("", botName, PlayerType.Bot, null, this.random.nextID()),
new PlayerInfo(
null,
"",
botName,
PlayerType.Bot,
null,
this.random.nextID(),
),
tile,
);
}
+1
View File
@@ -124,6 +124,7 @@ export class Executor {
new FakeHumanExecution(
this.gameID,
new PlayerInfo(
null,
nation.flag || "",
nation.name,
PlayerType.FakeHuman,
+1
View File
@@ -242,6 +242,7 @@ export class PlayerInfo {
public readonly clan: string | null;
constructor(
public readonly pattern: string,
public readonly flag: string,
public readonly name: string,
public readonly playerType: PlayerType,
+1
View File
@@ -88,6 +88,7 @@ export interface PlayerUpdate {
type: GameUpdateType.Player;
nameViewData?: NameViewData;
clientID: ClientID;
pattern: string;
flag: string;
name: string;
displayName: string;
+6
View File
@@ -177,6 +177,11 @@ export class PlayerView {
flag(): string {
return this.data.flag;
}
pattern(): string {
return this.data.pattern;
}
name(): string {
return userSettings.anonymousNames() && this.anonymousName !== null
? this.anonymousName
@@ -275,6 +280,7 @@ export class PlayerView {
}
info(): PlayerInfo {
return new PlayerInfo(
this.pattern(),
this.flag(),
this.name(),
this.type(),
+7
View File
@@ -80,6 +80,7 @@ export class PlayerImpl implements Player {
public _units: UnitImpl[] = [];
public _tiles: Set<TileRef> = new Set();
private _pattern: string;
private _flag: string;
private _name: string;
private _displayName: string;
@@ -107,6 +108,7 @@ export class PlayerImpl implements Player {
startTroops: number,
private readonly _team: Team | null,
) {
this._pattern = playerInfo.pattern;
this._flag = playerInfo.flag;
this._name = sanitizeUsername(playerInfo.name);
this._targetTroopRatio = 95n;
@@ -127,6 +129,7 @@ export class PlayerImpl implements Player {
return {
type: GameUpdateType.Player,
clientID: this.clientID(),
pattern: this.pattern(),
flag: this.flag(),
name: this.name(),
displayName: this.displayName(),
@@ -176,6 +179,10 @@ export class PlayerImpl implements Player {
return this._smallID;
}
pattern(): string {
return this._pattern;
}
flag(): string {
return this._flag;
}
+2
View File
@@ -33,6 +33,7 @@ describe("Attack", () => {
infiniteTroops: true,
});
const attackerInfo = new PlayerInfo(
null,
"us",
"attacker dude",
PlayerType.Human,
@@ -41,6 +42,7 @@ describe("Attack", () => {
);
game.addPlayer(attackerInfo);
const defenderInfo = new PlayerInfo(
null,
"us",
"defender dude",
PlayerType.Human,
+1
View File
@@ -32,6 +32,7 @@ describe("MissileSilo", () => {
beforeEach(async () => {
game = await setup("Plains", { infiniteGold: true, instantBuild: true });
const attacker_info = new PlayerInfo(
null,
"fr",
"attacker_id",
PlayerType.Human,
+9
View File
@@ -4,6 +4,7 @@ describe("PlayerInfo", () => {
describe("clan", () => {
test("should extract clan from name when format is [XX]Name", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[CL]PlayerName",
PlayerType.Human,
@@ -15,6 +16,7 @@ describe("PlayerInfo", () => {
test("should extract clan from name when format is [XXX]Name", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[ABC]PlayerName",
PlayerType.Human,
@@ -26,6 +28,7 @@ describe("PlayerInfo", () => {
test("should extract clan from name when format is [XXXX]Name", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[ABCD]PlayerName",
PlayerType.Human,
@@ -37,6 +40,7 @@ describe("PlayerInfo", () => {
test("should extract clan from name when format is [XXXXX]Name", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[ABCDE]PlayerName",
PlayerType.Human,
@@ -48,6 +52,7 @@ describe("PlayerInfo", () => {
test("should return null when name doesn't start with [", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"PlayerName",
PlayerType.Human,
@@ -59,6 +64,7 @@ describe("PlayerInfo", () => {
test("should return null when name doesn't contain ]", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[ABCPlayerName",
PlayerType.Human,
@@ -70,6 +76,7 @@ describe("PlayerInfo", () => {
test("should return null when clan tag is not 2-5 uppercase letters", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[A]PlayerName",
PlayerType.Human,
@@ -81,6 +88,7 @@ describe("PlayerInfo", () => {
test("should return null when clan tag contains non-uppercase letters", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[Abc]PlayerName",
PlayerType.Human,
@@ -92,6 +100,7 @@ describe("PlayerInfo", () => {
test("should return null when clan tag is too long", () => {
const playerInfo = new PlayerInfo(
null,
"fr",
"[ABCDEF]PlayerName",
PlayerType.Human,
+2
View File
@@ -18,6 +18,7 @@ describe("SAM", () => {
beforeEach(async () => {
game = await setup("Plains", { infiniteGold: true, instantBuild: true });
const defender_info = new PlayerInfo(
null,
"us",
"defender_id",
PlayerType.Human,
@@ -25,6 +26,7 @@ describe("SAM", () => {
"defender_id",
);
const attacker_info = new PlayerInfo(
null,
"fr",
"attacker_id",
PlayerType.Human,
+8 -1
View File
@@ -6,7 +6,14 @@ describe("Territory management", () => {
test("player owns the tile it spawns on", async () => {
const game = await setup("Plains");
game.addPlayer(
new PlayerInfo("us", "test_player", PlayerType.Human, null, "test_id"),
new PlayerInfo(
null,
"us",
"test_player",
PlayerType.Human,
null,
"test_id",
),
);
const spawnTile = game.map().ref(50, 50);
game.addExecution(
+2
View File
@@ -21,6 +21,7 @@ describe("Warship", () => {
instantBuild: true,
});
const player_1_info = new PlayerInfo(
null,
"us",
"boat dude",
PlayerType.Human,
@@ -29,6 +30,7 @@ describe("Warship", () => {
);
game.addPlayer(player_1_info);
const player_2_info = new PlayerInfo(
null,
"us",
"boat dude",
PlayerType.Human,