diff --git a/src/client/graphics/Utils.ts b/src/client/graphics/Utils.ts
index 3e838b242..6368fffa5 100644
--- a/src/client/graphics/Utils.ts
+++ b/src/client/graphics/Utils.ts
@@ -1,5 +1,3 @@
-import twemoji from 'twemoji';
-import DOMPurify from 'dompurify';
export function renderTroops(troops: number): string {
let troopsStr = ''
@@ -32,21 +30,3 @@ export function createCanvas(): HTMLCanvasElement {
return canvas
}
-
-export function processName(name: string): string {
- const sanitized = Array.from(name).slice(0, 10).join('').replace(/[^\p{L}\p{N}\s\p{Emoji}\p{Emoji_Component}]/gu, '');
-
- // First sanitize the raw input - strip everything except text and emojis
- const withEmojis = twemoji.parse(sanitized, {
- base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/', // Use jsDelivr CDN
- folder: 'svg', // or 'png' if you prefer
- ext: '.svg' // or '.png' if you prefer
- });
- return DOMPurify.sanitize(withEmojis, {
- ALLOWED_TAGS: ['img'],
- ALLOWED_ATTR: ['src', 'alt', 'class'],
- // Only allow twemoji CDN URLs
- ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/
- });
-
-}
\ No newline at end of file
diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts
index 17a2dd078..f51199d71 100644
--- a/src/client/graphics/layers/EventsDisplay.ts
+++ b/src/client/graphics/layers/EventsDisplay.ts
@@ -1,9 +1,9 @@
-import {nullable} from "zod";
-import {EventBus, GameEvent} from "../../../core/EventBus";
-import {AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, AllPlayers, BrokeAllianceEvent, EmojiMessageEvent, Game, Player, PlayerID, TargetPlayerEvent} from "../../../core/game/Game";
-import {ClientID} from "../../../core/Schemas";
-import {Layer} from "./Layer";
-import {SendAllianceReplyIntentEvent} from "../../Transport";
+import { nullable } from "zod";
+import { EventBus, GameEvent } from "../../../core/EventBus";
+import { AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, AllPlayers, BrokeAllianceEvent, EmojiMessageEvent, Game, Player, PlayerID, TargetPlayerEvent } from "../../../core/game/Game";
+import { ClientID } from "../../../core/Schemas";
+import { Layer } from "./Layer";
+import { SendAllianceReplyIntentEvent } from "../../Transport";
export enum MessageType {
SUCCESS,
@@ -234,7 +234,7 @@ export class EventsDisplay implements Layer {
}
if (event.message.recipient == myPlayer) {
this.addEvent({
- description: `${event.message.sender.name()}:${event.message.emoji}`,
+ description: `${event.message.sender.displayName()}:${event.message.emoji}`,
type: MessageType.INFO,
highlight: true,
createdAt: this.game.ticks(),
@@ -242,7 +242,7 @@ export class EventsDisplay implements Layer {
}
if (event.message.sender == myPlayer && event.message.recipient != AllPlayers) {
this.addEvent({
- description: `Sent ${event.message.recipient.name()} ${event.message.emoji}`,
+ description: `Sent ${event.message.recipient.displayName()} ${event.message.emoji}`,
type: MessageType.INFO,
highlight: true,
createdAt: this.game.ticks(),
diff --git a/src/client/graphics/layers/Leaderboard.ts b/src/client/graphics/layers/Leaderboard.ts
index 727b15169..8eb10bc8d 100644
--- a/src/client/graphics/layers/Leaderboard.ts
+++ b/src/client/graphics/layers/Leaderboard.ts
@@ -4,9 +4,6 @@ import { Layer } from './Layer';
import { Game, Player } from '../../../core/game/Game';
import { ClientID } from '../../../core/Schemas';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
-import { processName } from '../Utils';
-
-
interface Entry {
name: string
@@ -68,7 +65,7 @@ export class Leaderboard extends LitElement implements Layer {
this.players.pop()
this.players.push({
- name: myPlayer.name(),
+ name: myPlayer.displayName(),
position: place,
score: formatPercentage(myPlayer.numTilesOwned() / this.game.numLandTiles()),
isMyPlayer: true,
@@ -166,7 +163,7 @@ export class Leaderboard extends LitElement implements Layer {
.map((player, index) => html`
| ${player.position} |
- ${unsafeHTML(processName(player.name))} |
+ ${unsafeHTML(player.name)} |
${player.score} |
`)}
diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts
index f91fafdaf..ce4817911 100644
--- a/src/core/Schemas.ts
+++ b/src/core/Schemas.ts
@@ -7,7 +7,6 @@ export type ClientID = string
export type Intent = SpawnIntent
| AttackIntent
| BoatAttackIntent
- | UpdateNameIntent
| AllianceRequestIntent
| AllianceRequestReplyIntent
| BreakAllianceIntent
@@ -19,7 +18,6 @@ export type Intent = SpawnIntent
export type AttackIntent = z.infer
export type SpawnIntent = z.infer
export type BoatAttackIntent = z.infer
-export type UpdateNameIntent = z.infer
export type AllianceRequestIntent = z.infer
export type AllianceRequestReplyIntent = z.infer
export type BreakAllianceIntent = z.infer
@@ -101,11 +99,6 @@ export const BoatAttackIntentSchema = BaseIntentSchema.extend({
y: z.number(),
})
-export const UpdateNameIntentSchema = BaseIntentSchema.extend({
- type: z.literal('updateName'),
- name: z.string(),
-})
-
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
type: z.literal('allianceRequest'),
requestor: z.string(),
@@ -157,7 +150,6 @@ const IntentSchema = z.union([
AttackIntentSchema,
SpawnIntentSchema,
BoatAttackIntentSchema,
- UpdateNameIntentSchema,
AllianceRequestIntentSchema,
AllianceRequestReplyIntentSchema,
BreakAllianceIntentSchema,
diff --git a/src/core/Util.ts b/src/core/Util.ts
index 7d353c867..c0f12d386 100644
--- a/src/core/Util.ts
+++ b/src/core/Util.ts
@@ -1,7 +1,9 @@
-import {v4 as uuidv4} from 'uuid';
+import { v4 as uuidv4 } from 'uuid';
+import twemoji from 'twemoji';
+import DOMPurify from 'dompurify';
-import {Cell, Game, Player, TerraNullius, Tile} from "./game/Game";
+import { Cell, Game, Player, TerraNullius, Tile } from "./game/Game";
export function manhattanDist(c1: Cell, c2: Cell): number {
return Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y);
@@ -97,7 +99,7 @@ export function simpleHash(str: string): number {
return Math.abs(hash);
}
-export function calculateBoundingBox(borderTiles: ReadonlySet): {min: Cell; max: Cell} {
+export function calculateBoundingBox(borderTiles: ReadonlySet): { min: Cell; max: Cell } {
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
borderTiles.forEach((tile: Tile) => {
@@ -108,10 +110,10 @@ export function calculateBoundingBox(borderTiles: ReadonlySet): {min: Cell
maxY = Math.max(maxY, cell.y);
});
- return {min: new Cell(minX, minY), max: new Cell(maxX, maxY)}
+ return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }
}
-export function inscribed(outer: {min: Cell; max: Cell}, inner: {min: Cell; max: Cell}): boolean {
+export function inscribed(outer: { min: Cell; max: Cell }, inner: { min: Cell; max: Cell }): boolean {
return (
outer.min.x <= inner.min.x &&
outer.min.y <= inner.min.y &&
@@ -122,7 +124,7 @@ export function inscribed(outer: {min: Cell; max: Cell}, inner: {min: Cell; max:
export function getMode(list: string[]): string {
// Count occurrences
- const counts: {[key: string]: number} = {};
+ const counts: { [key: string]: number } = {};
for (const item of list) {
counts[item] = (counts[item] || 0) + 1;
}
@@ -139,4 +141,24 @@ export function getMode(list: string[]): string {
}
return mode;
+}
+
+export function sanitize(name: string): string {
+ return Array.from(name).slice(0, 10).join('').replace(/[^\p{L}\p{N}\s\p{Emoji}\p{Emoji_Component}]/gu, '');
+}
+
+export function processName(name: string): string {
+ // First sanitize the raw input - strip everything except text and emojis
+ const withEmojis = twemoji.parse(sanitize(name), {
+ base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/', // Use jsDelivr CDN
+ folder: 'svg', // or 'png' if you prefer
+ ext: '.svg' // or '.png' if you prefer
+ });
+ return DOMPurify.sanitize(withEmojis, {
+ ALLOWED_TAGS: ['img'],
+ ALLOWED_ATTR: ['src', 'alt', 'class'],
+ // Only allow twemoji CDN URLs
+ ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/
+ });
+
}
\ No newline at end of file
diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts
index 703660c1a..60bfafb0d 100644
--- a/src/core/execution/ExecutionManager.ts
+++ b/src/core/execution/ExecutionManager.ts
@@ -1,21 +1,20 @@
-import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType, Alliance, AllianceRequestReplyEvent, Difficulty} from "../game/Game";
-import {AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn} from "../Schemas";
-import {AttackExecution} from "./AttackExecution";
-import {SpawnExecution} from "./SpawnExecution";
-import {BotSpawner} from "./BotSpawner";
-import {BoatAttackExecution} from "./BoatAttackExecution";
-import {PseudoRandom} from "../PseudoRandom";
-import {UpdateNameExecution} from "./UpdateNameExecution";
-import {FakeHumanExecution} from "./FakeHumanExecution";
+import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType, Alliance, AllianceRequestReplyEvent, Difficulty } from "../game/Game";
+import { AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn } from "../Schemas";
+import { AttackExecution } from "./AttackExecution";
+import { SpawnExecution } from "./SpawnExecution";
+import { BotSpawner } from "./BotSpawner";
+import { BoatAttackExecution } from "./BoatAttackExecution";
+import { PseudoRandom } from "../PseudoRandom";
+import { FakeHumanExecution } from "./FakeHumanExecution";
import Usernames from '../../../resources/Usernames.txt'
-import {simpleHash} from "../Util";
-import {AllianceRequestExecution} from "./alliance/AllianceRequestExecution";
-import {AllianceRequestReplyExecution} from "./alliance/AllianceRequestReplyExecution";
-import {BreakAllianceExecution} from "./alliance/BreakAllianceExecution";
-import {TargetPlayerExecution} from "./TargetPlayerExecution";
-import {EmojiExecution} from "./EmojiExecution";
-import {DonateExecution} from "./DonateExecution";
-import {NukeExecution} from "./NukeExecution";
+import { processName, sanitize, simpleHash } from "../Util";
+import { AllianceRequestExecution } from "./alliance/AllianceRequestExecution";
+import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
+import { BreakAllianceExecution } from "./alliance/BreakAllianceExecution";
+import { TargetPlayerExecution } from "./TargetPlayerExecution";
+import { EmojiExecution } from "./EmojiExecution";
+import { DonateExecution } from "./DonateExecution";
+import { NukeExecution } from "./NukeExecution";
@@ -48,7 +47,7 @@ export class Executor {
)
} else if (intent.type == "spawn") {
return new SpawnExecution(
- new PlayerInfo(intent.name.slice(0, 18), intent.playerType, intent.clientID, intent.playerID),
+ new PlayerInfo(sanitize(intent.name), intent.playerType, intent.clientID, intent.playerID),
new Cell(intent.x, intent.y)
)
} else if (intent.type == "boat") {
@@ -58,11 +57,6 @@ export class Executor {
new Cell(intent.x, intent.y),
intent.troops
)
- } else if (intent.type == "updateName") {
- return new UpdateNameExecution(
- intent.name,
- intent.clientID
- )
} else if (intent.type == "allianceRequest") {
return new AllianceRequestExecution(intent.requestor, intent.recipient)
} else if (intent.type == "allianceRequestReply") {
diff --git a/src/core/execution/UpdateNameExecution.ts b/src/core/execution/UpdateNameExecution.ts
deleted file mode 100644
index 3702a1ab6..000000000
--- a/src/core/execution/UpdateNameExecution.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import {Execution, MutableGame, MutablePlayer, PlayerID} from "../game/Game"
-import {ClientID} from "../Schemas"
-
-export class UpdateNameExecution implements Execution {
-
- private active = true
- private mg: MutableGame
-
- constructor(private newName: string, private clientID: ClientID) {
- }
-
- init(mg: MutableGame, ticks: number) {
- this.mg = mg
- }
-
- tick(ticks: number) {
- const player = this.mg.players().find(p => p.clientID() == this.clientID)
- if (player == null) {
- return
- }
- player.setName(this.newName)
- this.active = false
- }
-
- owner(): MutablePlayer {
- return null
- }
-
- isActive(): boolean {
- return this.active
- }
- activeDuringSpawnPhase(): boolean {
- return true
- }
-}
\ No newline at end of file
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 0208dd4f4..a2a2b8ab0 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -163,6 +163,7 @@ export interface TerraNullius {
export interface Player {
info(): PlayerInfo
name(): string
+ displayName(): string
clientID(): ClientID
id(): PlayerID
type(): PlayerType
diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts
index e6226d98d..852aae510 100644
--- a/src/core/game/PlayerImpl.ts
+++ b/src/core/game/PlayerImpl.ts
@@ -1,12 +1,12 @@
-import {MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent, EmojiMessage, EmojiMessageEvent, AllPlayers, Currency} from "./Game";
-import {ClientID} from "../Schemas";
-import {simpleHash} from "../Util";
-import {CellString, GameImpl} from "./GameImpl";
-import {BoatImpl} from "./BoatImpl";
-import {TileImpl} from "./TileImpl";
-import {TerraNulliusImpl} from "./TerraNulliusImpl";
-import {MessageType} from "../../client/graphics/layers/EventsDisplay";
-import {renderTroops} from "../../client/graphics/Utils";
+import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent, EmojiMessage, EmojiMessageEvent, AllPlayers, Currency } from "./Game";
+import { ClientID } from "../Schemas";
+import { processName, simpleHash } from "../Util";
+import { CellString, GameImpl } from "./GameImpl";
+import { BoatImpl } from "./BoatImpl";
+import { TileImpl } from "./TileImpl";
+import { TerraNulliusImpl } from "./TerraNulliusImpl";
+import { MessageType } from "../../client/graphics/layers/EventsDisplay";
+import { renderTroops } from "../../client/graphics/Utils";
interface Target {
tick: Tick
@@ -29,6 +29,7 @@ export class PlayerImpl implements MutablePlayer {
public _tiles: Map = new Map();
private _name: string;
+ private _displayerName: string;
public pastOutgoingAllianceRequests: AllianceRequest[] = []
@@ -40,11 +41,15 @@ export class PlayerImpl implements MutablePlayer {
constructor(private gs: GameImpl, private readonly playerInfo: PlayerInfo, private _troops) {
this._name = playerInfo.name;
+ this._displayerName = processName(this._name)
}
name(): string {
return this._name;
}
+ displayName(): string {
+ return this._displayerName
+ }
clientID(): ClientID {
return this.playerInfo.clientID;
@@ -115,19 +120,19 @@ export class PlayerImpl implements MutablePlayer {
return toRemove
}
- isPlayer(): this is MutablePlayer {return true as const;}
- ownsTile(cell: Cell): boolean {return this._tiles.has(cell.toString());}
- setTroops(troops: number) {this._troops = Math.floor(troops);}
- conquer(tile: Tile) {this.gs.conquer(this, tile);}
+ isPlayer(): this is MutablePlayer { return true as const; }
+ ownsTile(cell: Cell): boolean { return this._tiles.has(cell.toString()); }
+ setTroops(troops: number) { this._troops = Math.floor(troops); }
+ conquer(tile: Tile) { this.gs.conquer(this, tile); }
relinquish(tile: Tile) {
if (tile.owner() != this) {
throw new Error(`Cannot relinquish tile not owned by this player`);
}
this.gs.relinquish(tile);
}
- info(): PlayerInfo {return this.playerInfo;}
- troops(): number {return this._troops;}
- isAlive(): boolean {return this._tiles.size > 0;}
+ info(): PlayerInfo { return this.playerInfo; }
+ troops(): number { return this._troops; }
+ isAlive(): boolean { return this._tiles.size > 0; }
executions(): Execution[] {
return this.gs.executions().filter(exec => exec.owner().id() == this.id());
}
@@ -204,7 +209,7 @@ export class PlayerImpl implements MutablePlayer {
}
target(other: Player): void {
- this.targets_.push({tick: this.gs.ticks(), target: other})
+ this.targets_.push({ tick: this.gs.ticks(), target: other })
this.gs.eventBus.emit(new TargetPlayerEvent(this, other))
}