Merge branch 'main' into main

This commit is contained in:
SunnyBoyWTF
2025-02-19 10:29:25 -05:00
committed by GitHub
37 changed files with 330 additions and 66 deletions
-2
View File
@@ -5,6 +5,4 @@ TODO.txt
resources/images/.DS_Store
resources/.DS_Store
.env
.prettierrc
.prettierignore
.DS_Store
+7
View File
@@ -0,0 +1,7 @@
*.png
*.jpg
*.jpeg
*.gif
*.webp
.prettierignore
.gitignore
+1
View File
@@ -0,0 +1 @@
{}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 167 KiB

+26 -15
View File
@@ -3,6 +3,7 @@ import { customElement, state } from "lit/decorators.js";
import { Lobby } from "../core/Schemas";
import { Difficulty, GameMapType, GameType } from "../core/game/Game";
import { consolex } from "../core/Consolex";
import { getMapsImage } from "./utilities/Maps";
@customElement("public-lobby")
export class PublicLobby extends LitElement {
@@ -80,23 +81,33 @@ export class PublicLobby extends LitElement {
: "bg-gradient-to-r from-blue-600 to-blue-500"} text-white font-medium rounded-xl transition-opacity duration-200 hover:opacity-90"
>
<div class="text-lg md:text-2xl font-semibold mb-2">Next Game</div>
<div class="flex items-center justify-center gap-4">
<div class="flex flex-col items-start">
<div class="text-md font-medium text-blue-100">
${lobby.gameConfig.gameMap}
<div class="flex">
<img
src="${getMapsImage(lobby.gameConfig.gameMap)}"
alt="${lobby.gameConfig.gameMap}"
class="w-1/3 md:w-1/5 md:h-[80px]"
style="border: 1px solid rgba(255, 255, 255, 0.5)"
/>
<div
class="w-full flex flex-col md:flex-row items-center justify-center gap-4"
>
<div class="flex flex-col items-start">
<div class="text-md font-medium text-blue-100">
${lobby.gameConfig.gameMap}
</div>
</div>
</div>
<div class="flex flex-col items-start">
<div class="text-md font-medium text-blue-100">
${lobby.numClients}
${lobby.numClients === 1 ? "Player" : "Players"} waiting
<div class="flex flex-col items-start">
<div class="text-md font-medium text-blue-100">
${lobby.numClients}
${lobby.numClients === 1 ? "Player" : "Players"} waiting
</div>
</div>
</div>
<div class="flex items-center">
<div
class="min-w-20 text-sm font-medium px-2 py-1 bg-white/10 rounded-xl text-blue-100 text-center"
>
${timeDisplay}
<div class="flex items-center">
<div
class="min-w-20 text-sm font-medium px-2 py-1 bg-white/10 rounded-xl text-blue-100 text-center"
>
${timeDisplay}
</div>
</div>
</div>
</div>
+13 -14
View File
@@ -26,10 +26,6 @@ import {
} from "../core/Schemas";
import { LobbyConfig } from "./ClientGameRunner";
import { LocalServer } from "./LocalServer";
import { UsernameInput } from "./UsernameInput";
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { PlayerView } from "../core/game/GameView";
export class PauseGameEvent implements GameEvent {
@@ -175,6 +171,7 @@ export class Transport {
ClientPingMessageSchema.parse({
type: "ping",
clientID: this.lobbyConfig.clientID,
persistentID: this.lobbyConfig.persistentID,
gameID: this.lobbyConfig.gameID,
}),
),
@@ -311,7 +308,7 @@ export class Transport {
this.sendIntent({
type: "allianceRequest",
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
playerID: event.requestor.id(),
recipient: event.recipient.id(),
});
}
@@ -321,7 +318,7 @@ export class Transport {
type: "allianceRequestReply",
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
recipient: event.recipient.id(),
playerID: event.recipient.id(),
accept: event.accepted,
});
}
@@ -330,7 +327,7 @@ export class Transport {
this.sendIntent({
type: "breakAlliance",
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
playerID: event.requestor.id(),
recipient: event.recipient.id(),
});
}
@@ -352,7 +349,7 @@ export class Transport {
this.sendIntent({
type: "attack",
clientID: this.lobbyConfig.clientID,
attackerID: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
targetID: event.targetID,
troops: event.troops,
});
@@ -362,7 +359,7 @@ export class Transport {
this.sendIntent({
type: "boat",
clientID: this.lobbyConfig.clientID,
attackerID: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
targetID: event.targetID,
troops: event.troops,
x: event.cell.x,
@@ -374,7 +371,7 @@ export class Transport {
this.sendIntent({
type: "targetPlayer",
clientID: this.lobbyConfig.clientID,
requestor: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
target: event.targetID,
});
}
@@ -383,7 +380,7 @@ export class Transport {
this.sendIntent({
type: "emoji",
clientID: this.lobbyConfig.clientID,
sender: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
recipient:
event.recipient == AllPlayers ? AllPlayers : event.recipient.id(),
emoji: event.emoji,
@@ -394,7 +391,7 @@ export class Transport {
this.sendIntent({
type: "donate",
clientID: this.lobbyConfig.clientID,
sender: event.sender.id(),
playerID: event.sender.id(),
recipient: event.recipient.id(),
troops: event.troops,
});
@@ -404,7 +401,7 @@ export class Transport {
this.sendIntent({
type: "troop_ratio",
clientID: this.lobbyConfig.clientID,
player: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
ratio: event.ratio,
});
}
@@ -413,7 +410,7 @@ export class Transport {
this.sendIntent({
type: "build_unit",
clientID: this.lobbyConfig.clientID,
player: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
unit: event.unit,
x: event.cell.x,
y: event.cell.y,
@@ -437,6 +434,7 @@ export class Transport {
const msg = ClientSendWinnerSchema.parse({
type: "winner",
clientID: this.lobbyConfig.clientID,
persistentID: this.lobbyConfig.persistentID,
gameID: this.lobbyConfig.gameID,
winner: event.winner,
});
@@ -455,6 +453,7 @@ export class Transport {
const msg = ClientIntentMessageSchema.parse({
type: "intent",
clientID: this.lobbyConfig.clientID,
persistentID: this.lobbyConfig.persistentID,
gameID: this.lobbyConfig.gameID,
intent: intent,
});
+19 -2
View File
@@ -52,6 +52,10 @@ export class ControlPanel extends LitElement implements Layer {
@state()
private _goldPerSecond: number;
private _lastPopulationIncreaseRate: number;
private _popRateIsIncreasing: boolean = true;
init() {
this.attackRatio = 0.2;
this.uiState.attackRatio = this.attackRatio;
@@ -63,12 +67,19 @@ export class ControlPanel extends LitElement implements Layer {
this.setVisibile(true);
}
const player = this.game.playerByClientID(this.clientID);
const player = this.game.myPlayer();
if (player == null || !player.isAlive()) {
this.setVisibile(false);
return;
}
const popIncreaseRate = player.population() - this._population;
if (this.game.ticks() % 5 == 0) {
this._popRateIsIncreasing =
popIncreaseRate >= this._lastPopulationIncreaseRate;
this._lastPopulationIncreaseRate = popIncreaseRate;
}
this._population = player.population();
this._maxPopulation = this.game.config().maxPopulation(player);
this._gold = player.gold();
@@ -78,6 +89,7 @@ export class ControlPanel extends LitElement implements Layer {
this._goldPerSecond = this.game.config().goldAdditionRate(player) * 10;
this.currentTroopRatio = player.troops() / player.population();
this.requestUpdate();
}
onAttackRatioChange(newRatio: number) {
@@ -163,7 +175,12 @@ export class ControlPanel extends LitElement implements Layer {
<span
>${renderTroops(this._population)} /
${renderTroops(this._maxPopulation)}
(+${renderTroops(this.popRate)})</span
<span
class="${this._popRateIsIncreasing
? "text-green-500"
: "text-yellow-500"}"
>(+${renderTroops(this.popRate)})</span
></span
>
</div>
<div class="flex justify-between">
+1 -1
View File
@@ -167,7 +167,7 @@
</a>
</div>
<div class="max-w-sm sm:max-w-md lg:max-w-lg xl:max-w-xl mx-auto p-2">
<div class="max-w-sm sm:max-w-md lg:max-w-lg xl:max-w-xl mx-auto mt-4">
<public-lobby class="w-full"></public-lobby>
</div>
+3
View File
@@ -5,6 +5,7 @@ import mena from "../../../resources/maps/Mena.png";
import northAmerica from "../../../resources/maps/NorthAmerica.png";
import blackSea from "../../../resources/maps/BlackSea.png";
import africa from "../../../resources/maps/Africa.png";
main
import { GameMapType } from "../../core/game/Game";
export function getMapsImage(map: GameMapType): string {
@@ -23,6 +24,8 @@ export function getMapsImage(map: GameMapType): string {
return blackSea;
case GameMapType.Africa
return Africa;
main
default:
return "";
}
+9
View File
@@ -47,4 +47,13 @@ export class PseudoRandom {
chance(odds: number): boolean {
return this.nextInt(0, odds) == 0;
}
shuffleArray(array: any[]) {
for (let i = array.length - 1; i >= 0; i--) {
const j = Math.floor(this.nextInt(0, i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
}
+12 -11
View File
@@ -133,11 +133,12 @@ const BaseIntentSchema = z.object({
"build_unit",
]),
clientID: ID,
playerID: ID,
});
export const AttackIntentSchema = BaseIntentSchema.extend({
type: z.literal("attack"),
attackerID: ID,
playerID: ID,
targetID: ID.nullable(),
troops: z.number().nullable(),
});
@@ -154,7 +155,7 @@ export const SpawnIntentSchema = BaseIntentSchema.extend({
export const BoatAttackIntentSchema = BaseIntentSchema.extend({
type: z.literal("boat"),
attackerID: ID,
playerID: ID,
targetID: ID.nullable(),
troops: z.number().nullable(),
x: z.number(),
@@ -163,52 +164,52 @@ export const BoatAttackIntentSchema = BaseIntentSchema.extend({
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
type: z.literal("allianceRequest"),
requestor: ID,
playerID: ID,
recipient: ID,
});
export const AllianceRequestReplyIntentSchema = BaseIntentSchema.extend({
type: z.literal("allianceRequestReply"),
requestor: ID, // The one who made the original alliance request
recipient: ID,
playerID: ID,
accept: z.boolean(),
});
export const BreakAllianceIntentSchema = BaseIntentSchema.extend({
type: z.literal("breakAlliance"),
requestor: ID, // The one who made the original alliance request
playerID: ID,
recipient: ID,
});
export const TargetPlayerIntentSchema = BaseIntentSchema.extend({
type: z.literal("targetPlayer"),
requestor: ID,
playerID: ID,
target: ID,
});
export const EmojiIntentSchema = BaseIntentSchema.extend({
type: z.literal("emoji"),
sender: ID,
playerID: ID,
recipient: z.union([ID, z.literal(AllPlayers)]),
emoji: EmojiSchema,
});
export const DonateIntentSchema = BaseIntentSchema.extend({
type: z.literal("donate"),
sender: ID,
playerID: ID,
recipient: ID,
troops: z.number().nullable(),
});
export const TargetTroopRatioIntentSchema = BaseIntentSchema.extend({
type: z.literal("troop_ratio"),
player: ID,
playerID: ID,
ratio: z.number().min(0).max(1),
});
export const BuildUnitIntentSchema = BaseIntentSchema.extend({
type: z.literal("build_unit"),
player: ID,
playerID: ID,
unit: z.nativeEnum(UnitType),
x: z.number(),
y: z.number(),
@@ -267,6 +268,7 @@ export const ServerMessageSchema = z.union([
const ClientBaseMessageSchema = z.object({
type: z.enum(["winner", "join", "intent", "ping", "log"]),
clientID: ID,
persistentID: SafeString.nullable(), // WARNING: persistent id is private.
gameID: ID,
});
@@ -294,7 +296,6 @@ export const ClientIntentMessageSchema = ClientBaseMessageSchema.extend({
// WARNING: never send this message to clients.
export const ClientJoinMessageSchema = ClientBaseMessageSchema.extend({
type: z.literal("join"),
persistentID: SafeString, // WARNING: persistent id is private.
lastTurn: z.number(), // The last turn the client saw.
username: SafeString,
});
+12 -1
View File
@@ -62,6 +62,17 @@ export class AttackExecution implements Execution {
}
this.mg = mg;
if (!mg.hasPlayer(this._ownerID)) {
console.warn(`player ${this._ownerID} not found`);
this.active = false;
return;
}
if (this._targetID != null && !mg.hasPlayer(this._targetID)) {
console.warn(`target ${this._targetID} not found`);
this.active = false;
return;
}
this._owner = mg.player(this._ownerID);
this.target =
this._targetID == this.mg.terraNullius().id()
@@ -90,8 +101,8 @@ export class AttackExecution implements Execution {
.config()
.attackAmount(this._owner, this.target);
}
this.startTroops = Math.min(this._owner.troops(), this.startTroops);
if (this.removeTroops) {
this.startTroops = Math.min(this._owner.troops(), this.startTroops);
this._owner.removeTroops(this.startTroops);
}
this.attack = this._owner.createAttack(
+5
View File
@@ -22,6 +22,11 @@ export class CityExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`CityExecution: player ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
@@ -36,6 +36,11 @@ export class ConstructionExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`ConstructionExecution: owner ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
@@ -23,6 +23,11 @@ export class DefensePostExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`DefensePostExectuion: owner ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
+11
View File
@@ -14,6 +14,17 @@ export class DonateExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`DonateExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(`DonateExecution recipient ${this.recipientID} not found`);
this.active = false;
return;
}
this.sender = mg.player(this.senderID);
this.recipient = mg.player(this.recipientID);
if (this.troops == null) {
+11
View File
@@ -22,6 +22,17 @@ export class EmojiExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`EmojiExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (this.recipientID != AllPlayers && !mg.hasPlayer(this.recipientID)) {
console.warn(`EmojiExecution: recipient ${this.recipientID} not found`);
this.active = false;
return;
}
this.requestor = mg.player(this.senderID);
this.recipient =
this.recipientID == AllPlayers ? AllPlayers : mg.player(this.recipientID);
+26 -10
View File
@@ -33,6 +33,7 @@ import { DonateExecution } from "./DonateExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { ConstructionExecution } from "./ConstructionExecution";
import { fixProfaneUsername, isProfaneUsername } from "../validations/username";
import { NoOpExecution } from "./NoOpExecution";
export class Executor {
// private random = new PseudoRandom(999)
@@ -52,11 +53,26 @@ export class Executor {
}
createExec(intent: Intent): Execution {
if (intent.type != "spawn") {
if (!this.mg.hasPlayer(intent.playerID)) {
console.warn(
`player ${intent.playerID} not found on intent ${intent.type}`,
);
return new NoOpExecution();
}
const player = this.mg.player(intent.playerID);
if (player.clientID() != intent.clientID) {
console.warn(
`intent ${intent.type} has incorrect clientID ${intent.clientID} for player ${player.name()} with clientID ${player.clientID()}`,
);
return new NoOpExecution();
}
}
switch (intent.type) {
case "attack": {
return new AttackExecution(
intent.troops,
intent.attackerID,
intent.playerID,
intent.targetID,
null,
);
@@ -77,40 +93,40 @@ export class Executor {
);
case "boat":
return new TransportShipExecution(
intent.attackerID,
intent.playerID,
intent.targetID,
this.mg.ref(intent.x, intent.y),
intent.troops,
);
case "allianceRequest":
return new AllianceRequestExecution(intent.requestor, intent.recipient);
return new AllianceRequestExecution(intent.playerID, intent.recipient);
case "allianceRequestReply":
return new AllianceRequestReplyExecution(
intent.requestor,
intent.recipient,
intent.playerID,
intent.accept,
);
case "breakAlliance":
return new BreakAllianceExecution(intent.requestor, intent.recipient);
return new BreakAllianceExecution(intent.playerID, intent.recipient);
case "targetPlayer":
return new TargetPlayerExecution(intent.requestor, intent.target);
return new TargetPlayerExecution(intent.playerID, intent.target);
case "emoji":
return new EmojiExecution(
intent.sender,
intent.playerID,
intent.recipient,
intent.emoji,
);
case "donate":
return new DonateExecution(
intent.sender,
intent.playerID,
intent.recipient,
intent.troops,
);
case "troop_ratio":
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
return new SetTargetTroopRatioExecution(intent.playerID, intent.ratio);
case "build_unit":
return new ConstructionExecution(
intent.player,
intent.playerID,
this.mg.ref(intent.x, intent.y),
intent.unit,
);
-3
View File
@@ -546,9 +546,6 @@ export class FakeHumanExecution implements Execution {
}
sendAttack(toAttack: Player | TerraNullius) {
console.log(
`${this.player.name()} sending troops ${renderTroops(this.player.troops() / 5)}`,
);
this.mg.addExecution(
new AttackExecution(
this.player.troops() / 5,
+6
View File
@@ -43,6 +43,12 @@ export class MirvExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`MIRVExecution: player ${this.senderID} not found`);
this.active = false;
return;
}
this.random = new PseudoRandom(mg.ticks() + simpleHash(this.senderID));
this.mg = mg;
this.pathFinder = PathFinder.Mini(mg, 10_000, true);
@@ -22,6 +22,12 @@ export class MissileSiloExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this._owner)) {
console.warn(`MissileSiloExecution: owner ${this._owner} not found`);
this.active = false;
return;
}
this.mg = mg;
this.player = mg.player(this._owner);
}
+15
View File
@@ -0,0 +1,15 @@
import { Execution, Game, Player } from "../game/Game";
export class NoOpExecution implements Execution {
isActive(): boolean {
return false;
}
activeDuringSpawnPhase(): boolean {
return false;
}
init(mg: Game, ticks: number): void {}
tick(ticks: number): void {}
owner(): Player {
return null;
}
}
+7 -6
View File
@@ -33,6 +33,12 @@ export class NukeExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`NukeExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
this.mg = mg;
this.player = mg.player(this.senderID);
this.random = new PseudoRandom(ticks);
@@ -120,18 +126,13 @@ export class NukeExecution implements Execution {
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer;
});
const ratio = Object.fromEntries(
this.mg
.players()
.map((p) => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()]),
);
const attacked = new Map<Player, number>();
for (const tile of toDestroy) {
const owner = this.mg.owner(tile);
if (owner.isPlayer()) {
const mp = this.mg.player(owner.id());
mp.relinquish(tile);
mp.removeTroops(2 * ratio[mp.id()]);
mp.removeTroops((5 * mp.population()) / mp.numTilesOwned());
if (!attacked.has(mp)) {
attacked.set(mp, 0);
}
+5
View File
@@ -28,6 +28,11 @@ export class PlayerExecution implements Execution {
}
init(mg: Game, ticks: number) {
if (!mg.hasPlayer(this.playerID)) {
console.warn(`PlayerExecution: player ${this.playerID} not found`);
this.active = false;
return;
}
this.mg = mg;
this.config = mg.config();
this.player = mg.player(this.playerID);
+5
View File
@@ -31,6 +31,11 @@ export class PortExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this._owner)) {
console.warn(`PortExecution: player ${this._owner} not found`);
this.active = false;
return;
}
this.mg = mg;
this.random = new PseudoRandom(mg.ticks());
}
@@ -12,6 +12,11 @@ export class SetTargetTroopRatioExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.playerID)) {
console.warn(
`SetTargetTRoopRatioExecution: player ${this.playerID} not found`,
);
}
this.player = mg.player(this.playerID);
}
+1
View File
@@ -28,6 +28,7 @@ export class SpawnExecution implements Execution {
this.active = false;
if (!this.mg.inSpawnPhase()) {
this.active = false;
return;
}
@@ -12,6 +12,19 @@ export class TargetPlayerExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`TargetPlayerExecution: requestor ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.targetID)) {
console.warn(`TargetPlayerExecution: target ${this.targetID} not found`);
this.active = false;
return;
}
this.requestor = mg.player(this.requestorID);
this.target = mg.player(this.targetID);
}
@@ -50,6 +50,19 @@ export class TransportShipExecution implements Execution {
}
init(mg: Game, ticks: number) {
if (!mg.hasPlayer(this.attackerID)) {
console.warn(
`TransportShipExecution: attacker ${this.attackerID} not found`,
);
this.active = false;
return;
}
if (this.targetID != null && !mg.hasPlayer(this.targetID)) {
console.warn(`TransportShipExecution: target ${this.targetID} not found`);
this.active = false;
return;
}
this.lastMove = ticks;
this.mg = mg;
this.pathFinder = PathFinder.Mini(mg, 10_000, false, 2);
+5
View File
@@ -43,6 +43,11 @@ export class WarshipExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.playerID)) {
console.log(`WarshipExecution: player ${this.playerID} not found`);
this.active = false;
return;
}
this.pathfinder = PathFinder.Mini(mg, 5000, false);
this._owner = mg.player(this.playerID);
this.mg = mg;
@@ -19,6 +19,21 @@ export class AllianceRequestExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`AllianceRequestExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`AllianceRequestExecution recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.mg = mg;
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
@@ -20,6 +20,20 @@ export class AllianceRequestReplyExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`AllianceRequestReplyExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`AllianceRequestReplyExecution recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.mg = mg;
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
@@ -19,6 +19,20 @@ export class BreakAllianceExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`BreakAllianceExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`BreakAllianceExecution: recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
this.mg = mg;
+27 -1
View File
@@ -9,6 +9,7 @@ import { PseudoRandom } from "../core/PseudoRandom";
export class GameManager {
private lastNewLobby: number = 0;
private mapsPlaylist: GameMapType[] = [];
private games: GameServer[] = [];
@@ -77,6 +78,31 @@ export class GameManager {
}
}
private getNextMap(): GameMapType {
if (this.mapsPlaylist.length > 0) {
return this.mapsPlaylist.shift();
}
while (true) {
this.mapsPlaylist = Object.values(GameMapType);
this.mapsPlaylist.push(GameMapType.World);
this.mapsPlaylist.push(GameMapType.Europe);
this.random.shuffleArray(this.mapsPlaylist);
if (this.allNonConsecutive(this.mapsPlaylist)) {
return this.mapsPlaylist.shift();
}
}
}
private allNonConsecutive(maps: GameMapType[]): boolean {
// Check for consecutive duplicates in the maps array
for (let i = 0; i < maps.length - 1; i++) {
if (maps[i] === maps[i + 1]) {
return false;
}
}
return true;
}
tick() {
const lobbies = this.gamesByPhase(GamePhase.Lobby);
const active = this.gamesByPhase(GamePhase.Active);
@@ -87,7 +113,7 @@ export class GameManager {
this.lastNewLobby = now;
lobbies.push(
new GameServer(generateID(), now, true, this.config, {
gameMap: this.random.randElement(Object.values(GameMapType)),
gameMap: this.getNextMap(),
gameType: GameType.Public,
difficulty: Difficulty.Medium,
disableBots: false,
+13
View File
@@ -103,6 +103,19 @@ export class GameServer {
const clientMsg: ClientMessage = ClientMessageSchema.parse(
JSON.parse(message),
);
if (this.allClients.has(clientMsg.clientID)) {
const client = this.allClients.get(clientMsg.clientID);
if (client.persistentID != clientMsg.persistentID) {
console.warn(
`Client ID ${clientMsg.clientID} sent incorrect id ${clientMsg.persistentID}, does not match persistent id ${client.persistentID}`,
);
return;
}
}
// Clear out persistent id to make sure it doesn't get sent to other clients.
clientMsg.persistentID = null;
if (clientMsg.type == "intent") {
if (clientMsg.gameID == this.id) {
this.addIntent(clientMsg.intent);