diff --git a/README.md b/README.md
index 46ec42fa8..a001bc954 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,16 @@ Contributions are welcome! Please feel free to submit a Pull Request.
4. Push to the branch (`git push origin amazing-feature`)
5. Open a Pull Request
+## 🌐 Translation
+
+Translators are welcome! Please feel free to help translate into your language.
+How to help?
+
+1. Go to the project's Crowdin translation page: [https://crowdin.com/project/openfrontio](https://crowdin.com/project/openfrontio)
+2. Login if you already have an account/ Sign up if you don't have one
+3. Select the language you want to translate in/ If your language isn't on the list, create a new topic in "Discussions" about adding the language
+4. Translate the strings
+
### Project Governance
- The project maintainer ([evan](https://github.com/evanpelle)) has final authority on all code changes and design decisions
diff --git a/resources/flags/uk_us_flag.svg b/resources/flags/uk_us_flag.svg
new file mode 100644
index 000000000..a4f09a725
--- /dev/null
+++ b/resources/flags/uk_us_flag.svg
@@ -0,0 +1,112 @@
+
+
diff --git a/resources/lang/bg.json b/resources/lang/bg.json
index 1e4cc641e..da444e6c0 100644
--- a/resources/lang/bg.json
+++ b/resources/lang/bg.json
@@ -111,7 +111,8 @@
"pangaea": "Пангея",
"map": "Карта",
"betweentwoseas": "Между Две Морета",
- "japanandneighbors": "Япония и съседи"
+ "japan": "Япония и съседи",
+ "knownworld": "Познат Свят"
},
"private_lobby": {
"title": "Присъединяване към частна игра",
@@ -169,7 +170,7 @@
"lang_code": "bg"
},
"game_mode": {
- "ffa": "Свободна игра (FFA)",
+ "ffa": "Всеки срещу всеки (FFA)",
"teams": "Отбори"
}
}
diff --git a/resources/lang/de.json b/resources/lang/de.json
index 6d65b8bcb..3b5466e09 100644
--- a/resources/lang/de.json
+++ b/resources/lang/de.json
@@ -111,7 +111,8 @@
"pangaea": "Pangaea",
"map": "Karte",
"betweentwoseas": "Zwischen zwei Meeren",
- "japanandneighbors": "Japan und Nachbarländer"
+ "japan": "Japan und Nachbarländer",
+ "knownworld": "Bekannte Welt"
},
"private_lobby": {
"title": "Privater Lobby beitreten",
diff --git a/resources/lang/en.json b/resources/lang/en.json
index cedf01779..06ae51508 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -2,7 +2,7 @@
"lang": {
"en": "English",
"native": "English",
- "svg": "xx",
+ "svg": "uk_us_flag",
"lang_code": "en"
},
"main": {
@@ -22,6 +22,7 @@
"action_alt_view": "Alternate view (terrain/countries)",
"action_attack_altclick": "Attack (when left click is set to open menu)",
"action_build": "Open build menu",
+ "action_emote": "Open emote menu",
"action_center": "Center camera on player",
"action_zoom": "Zoom out/in",
"action_move_camera": "Move camera",
diff --git a/resources/lang/nl.json b/resources/lang/nl.json
index 3940c397a..428ca3204 100644
--- a/resources/lang/nl.json
+++ b/resources/lang/nl.json
@@ -109,7 +109,10 @@
"random": "Willekeurig",
"iceland": "IJsland",
"pangaea": "Pangea",
- "map": "Kaart"
+ "map": "Kaart",
+ "betweentwoseas": "Tussen twee zeeën",
+ "japan": "Japan en buren",
+ "knownworld": "Bekende Wereld"
},
"private_lobby": {
"title": "Privélobby toetreden",
diff --git a/resources/lang/pl.json b/resources/lang/pl.json
index 8fdec8e07..30118ab54 100644
--- a/resources/lang/pl.json
+++ b/resources/lang/pl.json
@@ -109,7 +109,10 @@
"random": "Losowe",
"iceland": "Islandia",
"pangaea": "Pangea",
- "map": "Mapa"
+ "map": "Mapa",
+ "betweentwoseas": "Między dwoma morzami",
+ "japan": "Japonia i sąsiedzi",
+ "knownworld": "Znany Świat"
},
"private_lobby": {
"title": "Dołącz do prywatnego Lobby",
@@ -167,7 +170,7 @@
"lang_code": "pl"
},
"game_mode": {
- "ffa": "Darmowe dla wszystkich",
+ "ffa": "Każdy na Każdego",
"teams": "Drużyny"
}
}
diff --git a/resources/lang/ru.json b/resources/lang/ru.json
index 42d438c35..1587414f4 100644
--- a/resources/lang/ru.json
+++ b/resources/lang/ru.json
@@ -111,7 +111,8 @@
"pangaea": "Пангея",
"map": "Карта",
"betweentwoseas": "Между двух морей",
- "japanandneighbors": "Япония и соседи"
+ "japan": "Япония и соседи",
+ "knownworld": "Известный мир"
},
"private_lobby": {
"title": "Присоединиться к приватному лобби",
diff --git a/resources/lang/uk.json b/resources/lang/uk.json
index 0ff47257a..9597207e1 100644
--- a/resources/lang/uk.json
+++ b/resources/lang/uk.json
@@ -111,7 +111,8 @@
"pangaea": "Пангея",
"map": "Мапа",
"betweentwoseas": "Поміж двох морів",
- "japanandneighbors": "Японія та сусіди"
+ "japan": "Японія та сусіди",
+ "knownworld": "Відомий світ"
},
"private_lobby": {
"title": "Приєднатися до приватного лобі",
diff --git a/resources/maps/TwoSeas.bin b/resources/maps/BetweenTwoSeas.bin
similarity index 100%
rename from resources/maps/TwoSeas.bin
rename to resources/maps/BetweenTwoSeas.bin
diff --git a/resources/maps/TwoSeas.json b/resources/maps/BetweenTwoSeas.json
similarity index 98%
rename from resources/maps/TwoSeas.json
rename to resources/maps/BetweenTwoSeas.json
index 38bdaa90d..ea35ffa16 100644
--- a/resources/maps/TwoSeas.json
+++ b/resources/maps/BetweenTwoSeas.json
@@ -1,5 +1,5 @@
{
- "name": "TwoSeas",
+ "name": "BetweenTwoSeas",
"width": 1778,
"height": 1062,
"nations": [
diff --git a/resources/maps/TwoSeas.png b/resources/maps/BetweenTwoSeas.png
similarity index 100%
rename from resources/maps/TwoSeas.png
rename to resources/maps/BetweenTwoSeas.png
diff --git a/resources/maps/TwoSeasMini.bin b/resources/maps/BetweenTwoSeasMini.bin
similarity index 100%
rename from resources/maps/TwoSeasMini.bin
rename to resources/maps/BetweenTwoSeasMini.bin
diff --git a/resources/maps/TwoSeasThumb.webp b/resources/maps/BetweenTwoSeasThumb.webp
similarity index 100%
rename from resources/maps/TwoSeasThumb.webp
rename to resources/maps/BetweenTwoSeasThumb.webp
diff --git a/src/client/HelpModal.ts b/src/client/HelpModal.ts
index bbf842eb9..8de819377 100644
--- a/src/client/HelpModal.ts
+++ b/src/client/HelpModal.ts
@@ -46,6 +46,10 @@ export class HelpModal extends LitElement {
Ctrl + left click |
${translateText("help_modal.action_build")} |
+
+ | Alt + left click |
+ ${translateText("help_modal.action_emote")} |
+
| C |
${translateText("help_modal.action_center")} |
diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts
index 806bc6cf8..ae495e9fe 100644
--- a/src/client/InputHandler.ts
+++ b/src/client/InputHandler.ts
@@ -69,6 +69,12 @@ export class ShowBuildMenuEvent implements GameEvent {
public readonly y: number,
) {}
}
+export class ShowEmojiMenuEvent implements GameEvent {
+ constructor(
+ public readonly x: number,
+ public readonly y: number,
+ ) {}
+}
export class AttackRatioEvent implements GameEvent {
constructor(public readonly attackRatio: number) {}
@@ -292,6 +298,10 @@ export class InputHandler {
this.eventBus.emit(new ShowBuildMenuEvent(event.clientX, event.clientY));
return;
}
+ if (event.altKey) {
+ this.eventBus.emit(new ShowEmojiMenuEvent(event.clientX, event.clientY));
+ return;
+ }
const dist =
Math.abs(event.x - this.lastPointerDownX) +
diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts
index 7faaa92c1..fbc23d0bd 100644
--- a/src/client/SinglePlayerModal.ts
+++ b/src/client/SinglePlayerModal.ts
@@ -11,6 +11,7 @@ import "./components/Difficulties";
import { DifficultyDescription } from "./components/Difficulties";
import "./components/Maps";
import { JoinLobbyEvent } from "./Main";
+import { UsernameInput } from "./UsernameInput";
@customElement("single-player-modal")
export class SinglePlayerModal extends LitElement {
@@ -326,6 +327,13 @@ export class SinglePlayerModal extends LitElement {
const clientID = generateID();
const gameID = generateID();
+ const usernameInput = document.querySelector(
+ "username-input",
+ ) as UsernameInput;
+ if (!usernameInput) {
+ consolex.warn("Username input element not found");
+ }
+
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
@@ -337,7 +345,7 @@ export class SinglePlayerModal extends LitElement {
{
playerID: generateID(),
clientID,
- username: "PLACEHOLDER",
+ username: usernameInput.getCurrentUsername(),
},
],
config: {
diff --git a/src/client/components/Maps.ts b/src/client/components/Maps.ts
index 1ff02fe1c..f4d419185 100644
--- a/src/client/components/Maps.ts
+++ b/src/client/components/Maps.ts
@@ -21,7 +21,7 @@ export const MapDescription: Record = {
Australia: "Australia",
Iceland: "Iceland",
Japan: "Japan",
- TwoSeas: "Between Two Seas",
+ BetweenTwoSeas: "Between Two Seas",
KnownWorld: "Known World",
};
diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts
index 5f76b79e7..fe743ef54 100644
--- a/src/client/graphics/GameRenderer.ts
+++ b/src/client/graphics/GameRenderer.ts
@@ -47,6 +47,11 @@ export function createRenderer(
if (!emojiTable || !(emojiTable instanceof EmojiTable)) {
consolex.error("EmojiTable element not found in the DOM");
}
+ emojiTable.eventBus = eventBus;
+ emojiTable.transformHandler = transformHandler;
+ emojiTable.game = game;
+ emojiTable.initEventBus();
+
const buildMenu = document.querySelector("build-menu") as BuildMenu;
if (!buildMenu || !(buildMenu instanceof BuildMenu)) {
consolex.error("BuildMenu element not found in the DOM");
diff --git a/src/client/graphics/layers/EmojiTable.ts b/src/client/graphics/layers/EmojiTable.ts
index 06edeecb0..5db92c866 100644
--- a/src/client/graphics/layers/EmojiTable.ts
+++ b/src/client/graphics/layers/EmojiTable.ts
@@ -1,5 +1,12 @@
import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
+import { EventBus } from "../../../core/EventBus";
+import { AllPlayers } from "../../../core/game/Game";
+import { GameView, PlayerView } from "../../../core/game/GameView";
+import { TerraNulliusImpl } from "../../../core/game/TerraNulliusImpl";
+import { ShowEmojiMenuEvent } from "../../InputHandler";
+import { SendEmojiIntentEvent } from "../../Transport";
+import { TransformHandler } from "../TransformHandler";
const emojiTable: string[][] = [
["😀", "😊", "🥰", "😇", "😎"],
@@ -17,6 +24,10 @@ const emojiTable: string[][] = [
@customElement("emoji-table")
export class EmojiTable extends LitElement {
+ public eventBus: EventBus;
+ public transformHandler: TransformHandler;
+ public game: GameView;
+
static styles = css`
:host {
display: block;
@@ -96,6 +107,35 @@ export class EmojiTable extends LitElement {
@state()
private _hidden = true;
+ initEventBus() {
+ this.eventBus.on(ShowEmojiMenuEvent, (e) => {
+ const cell = this.transformHandler.screenToWorldCoordinates(e.x, e.y);
+ if (!this.game.isValidCoord(cell.x, cell.y)) {
+ return;
+ }
+
+ const tile = this.game.ref(cell.x, cell.y);
+ if (!this.game.hasOwner(tile)) {
+ return;
+ }
+
+ const targetPlayer = this.game.owner(tile);
+ // maybe redundant due to owner check but better safe than sorry
+ if (targetPlayer instanceof TerraNulliusImpl) {
+ return;
+ }
+
+ this.showTable((emoji) => {
+ const recipient =
+ targetPlayer == this.game.myPlayer()
+ ? AllPlayers
+ : (targetPlayer as PlayerView);
+ this.eventBus.emit(new SendEmojiIntentEvent(recipient, emoji));
+ this.hideTable();
+ });
+ });
+ }
+
private onEmojiClicked: (emoji: string) => void = () => {};
render() {
diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts
index 6f38b380a..7b421237b 100644
--- a/src/client/graphics/layers/WinModal.ts
+++ b/src/client/graphics/layers/WinModal.ts
@@ -215,7 +215,8 @@ export class WinModal extends LitElement implements Layer {
!this.hasShownDeathModal &&
myPlayer &&
!myPlayer.isAlive() &&
- !this.game.inSpawnPhase()
+ !this.game.inSpawnPhase() &&
+ myPlayer.hasSpawned()
) {
this.hasShownDeathModal = true;
this._title = "You died";
diff --git a/src/client/styles.css b/src/client/styles.css
index 9f93d919e..b7c531151 100644
--- a/src/client/styles.css
+++ b/src/client/styles.css
@@ -240,28 +240,53 @@ label.option-card:hover {
#private-lobby-bots-count {
width: 80%;
height: 16px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
appearance: none;
}
+/* Firefox */
+#bots-count::-moz-range-track,
+#private-lobby-bots-count::-moz-range-track {
+ height: 8px;
+ background: white;
+}
+
+#bots-count::-moz-range-progress,
+#private-lobby-bots-count::-moz-range-progress {
+ height: 8px;
+ background-color: #0075ff;
+}
+
+#bots-count::-moz-range-thumb,
+#private-lobby-bots-count::-moz-range-thumb {
+ height: 16px;
+ width: 16px;
+ background: #0075ff;
+ border: none;
+ border-radius: 50%;
+}
+
+/* Chrome */
#bots-count::-webkit-slider-runnable-track,
#private-lobby-bots-count::-webkit-slider-runnable-track {
- appearance: none;
+ height: 8px;
background: linear-gradient(
to right,
#0075ff var(--progress, 0%),
white var(--progress, 0%)
);
- height: 8px;
}
#bots-count::-webkit-slider-thumb,
#private-lobby-bots-count::-webkit-slider-thumb {
-webkit-appearance: none;
- appearance: none;
+ height: 16px;
+ width: 16px;
background: #0075ff;
- border-color: #0075ff;
- position: relative;
- top: -3px;
+ border: none;
+ border-radius: 50%;
+ margin-top: -4px;
}
.random-map {
diff --git a/src/client/utilities/Maps.ts b/src/client/utilities/Maps.ts
index d3be87796..a616f7310 100644
--- a/src/client/utilities/Maps.ts
+++ b/src/client/utilities/Maps.ts
@@ -1,6 +1,7 @@
import africa from "../../../resources/maps/AfricaThumb.webp";
import asia from "../../../resources/maps/AsiaThumb.webp";
import australia from "../../../resources/maps/AustraliaThumb.webp";
+import betweenTwoSeas from "../../../resources/maps/BetweenTwoSeasThumb.webp";
import blackSea from "../../../resources/maps/BlackSeaThumb.webp";
import britannia from "../../../resources/maps/BritanniaThumb.webp";
import europe from "../../../resources/maps/EuropeThumb.webp";
@@ -14,7 +15,6 @@ import northAmerica from "../../../resources/maps/NorthAmericaThumb.webp";
import oceania from "../../../resources/maps/OceaniaThumb.webp";
import pangaea from "../../../resources/maps/PangaeaThumb.webp";
import southAmerica from "../../../resources/maps/SouthAmericaThumb.webp";
-import twoSeas from "../../../resources/maps/TwoSeasThumb.webp";
import world from "../../../resources/maps/WorldMapThumb.webp";
import { GameMapType } from "../../core/game/Game";
@@ -53,8 +53,8 @@ export function getMapsImage(map: GameMapType): string {
return iceland;
case GameMapType.Japan:
return japan;
- case GameMapType.TwoSeas:
- return twoSeas;
+ case GameMapType.BetweenTwoSeas:
+ return betweenTwoSeas;
case GameMapType.KnownWorld:
return knownworld;
default:
diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts
index cc7e24b2a..13d8fcc10 100644
--- a/src/core/GameRunner.ts
+++ b/src/core/GameRunner.ts
@@ -25,6 +25,8 @@ import {
} from "./game/GameUpdates";
import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader";
import { ClientID, GameStartInfo, Turn } from "./Schemas";
+import { sanitize } from "./Util";
+import { fixProfaneUsername } from "./validations/username";
export async function createGameRunner(
gameStart: GameStartInfo,
@@ -38,7 +40,9 @@ export async function createGameRunner(
(p) =>
new PlayerInfo(
p.flag,
- p.username,
+ p.clientID == clientID
+ ? sanitize(p.username)
+ : fixProfaneUsername(sanitize(p.username)),
PlayerType.Human,
p.clientID,
p.playerID,
diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts
index b11aa63ec..7d2d7b99c 100644
--- a/src/core/configuration/DefaultConfig.ts
+++ b/src/core/configuration/DefaultConfig.ts
@@ -95,9 +95,11 @@ export abstract class DefaultServerConfig implements ServerConfig {
}
// Maps smaller than ~2 mil pixels
if (
- [GameMapType.TwoSeas, GameMapType.BlackSea, GameMapType.Pangaea].includes(
- map,
- )
+ [
+ GameMapType.BetweenTwoSeas,
+ GameMapType.BlackSea,
+ GameMapType.Pangaea,
+ ].includes(map)
) {
return Math.random() < 0.2 ? 60 : 35;
}
@@ -266,7 +268,7 @@ export class DefaultConfig implements Config {
case UnitType.AtomBomb:
return {
cost: (p: Player) =>
- p.type() == PlayerType.Human && this.infiniteGold() ? 0 : 750_000,
+ p.type() == PlayerType.Human && this.infiniteGold() ? 0 : 500_000,
territoryBound: false,
};
case UnitType.HydrogenBomb:
diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts
index ad6e95d4d..968455cc1 100644
--- a/src/core/execution/ExecutionManager.ts
+++ b/src/core/execution/ExecutionManager.ts
@@ -1,8 +1,7 @@
import { Execution, Game, PlayerInfo, PlayerType } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { ClientID, GameID, Intent, Turn } from "../Schemas";
-import { sanitize, simpleHash } from "../Util";
-import { fixProfaneUsername } from "../validations/username";
+import { simpleHash } from "../Util";
import { AllianceRequestExecution } from "./alliance/AllianceRequestExecution";
import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
import { BreakAllianceExecution } from "./alliance/BreakAllianceExecution";
@@ -62,16 +61,7 @@ export class Executor {
return new MoveWarshipExecution(intent.unitId, intent.tile);
case "spawn":
return new SpawnExecution(
- new PlayerInfo(
- intent.flag,
- // Players see their original name, others see a sanitized version
- intent.clientID == this.clientID
- ? sanitize(intent.name)
- : fixProfaneUsername(sanitize(intent.name)),
- PlayerType.Human,
- intent.clientID,
- playerID,
- ),
+ player.info(),
this.mg.ref(intent.x, intent.y),
);
case "boat":
diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts
index eed34f200..e5d4cba60 100644
--- a/src/core/execution/FakeHumanExecution.ts
+++ b/src/core/execution/FakeHumanExecution.ts
@@ -16,7 +16,7 @@ import {
Unit,
UnitType,
} from "../game/Game";
-import { andFN, euclDistFN, manhattanDistFN, TileRef } from "../game/GameMap";
+import { euclDistFN, manhattanDistFN, TileRef } from "../game/GameMap";
import { PseudoRandom } from "../PseudoRandom";
import { GameID } from "../Schemas";
import { calculateBoundingBox, simpleHash } from "../Util";
@@ -169,12 +169,12 @@ export class FakeHumanExecution implements Execution {
if (enemyborder.length == 0) {
if (this.random.chance(5)) {
- this.sendBoat();
+ this.sendBoatRandomly();
}
return;
}
if (this.random.chance(10)) {
- this.sendBoat();
+ this.sendBoatRandomly();
return;
}
@@ -588,58 +588,30 @@ export class FakeHumanExecution implements Execution {
);
}
- sendBoat(tries: number = 0, oceanShore: TileRef[] = null) {
- if (tries > 10) {
- return;
- }
-
- if (oceanShore == null) {
- oceanShore = Array.from(this.player.borderTiles()).filter((t) =>
- this.mg.isOceanShore(t),
- );
- }
+ sendBoatRandomly() {
+ const oceanShore = Array.from(this.player.borderTiles()).filter((t) =>
+ this.mg.isOceanShore(t),
+ );
if (oceanShore.length == 0) {
return;
}
const src = this.random.randElement(oceanShore);
- const otherShore = Array.from(
- this.mg.bfs(
- src,
- andFN(
- (gm, t) => gm.isOcean(t) || gm.isOceanShore(t),
- manhattanDistFN(src, 200),
- ),
+
+ const dst = this.randOceanShoreTile(src, 250);
+ if (dst == null) {
+ return;
+ }
+
+ this.mg.addExecution(
+ new TransportShipExecution(
+ this.player.id(),
+ this.mg.owner(dst).id(),
+ dst,
+ this.player.troops() / 5,
),
- ).filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) != this.player);
-
- if (otherShore.length == 0) {
- return;
- }
-
- for (let i = 0; i < 20; i++) {
- const dst = this.random.randElement(otherShore);
- if (this.isSmallIsland(dst)) {
- continue;
- }
- if (
- this.mg.owner(dst).isPlayer() &&
- this.player.isFriendly(this.mg.owner(dst) as Player)
- ) {
- continue;
- }
-
- this.mg.addExecution(
- new TransportShipExecution(
- this.player.id(),
- this.mg.hasOwner(dst) ? this.mg.owner(dst).id() : null,
- dst,
- this.player.troops() / 5,
- ),
- );
- return;
- }
- this.sendBoat(tries + 1, oceanShore);
+ );
+ return;
}
randomLand(): TileRef | null {
@@ -684,13 +656,28 @@ export class FakeHumanExecution implements Execution {
);
}
- isSmallIsland(tile: TileRef): boolean {
- return (
- this.mg.bfs(
- tile,
- andFN((gm, t) => gm.isLand(t), manhattanDistFN(tile, 10)),
- ).size < 50
- );
+ private randOceanShoreTile(tile: TileRef, dist: number): TileRef | null {
+ const x = this.mg.x(tile);
+ const y = this.mg.y(tile);
+ for (let i = 0; i < 500; i++) {
+ const randX = this.random.nextInt(x - dist, x + dist);
+ const randY = this.random.nextInt(y - dist, y + dist);
+ if (!this.mg.isValidCoord(randX, randY)) {
+ continue;
+ }
+ const randTile = this.mg.ref(randX, randY);
+ if (!this.mg.isOceanShore(randTile)) {
+ continue;
+ }
+ const owner = this.mg.owner(randTile);
+ if (!owner.isPlayer()) {
+ return randTile;
+ }
+ if (!owner.isFriendly(this.player)) {
+ return randTile;
+ }
+ }
+ return null;
}
owner(): Player {
diff --git a/src/core/execution/SpawnExecution.ts b/src/core/execution/SpawnExecution.ts
index d315a0a53..74e1df782 100644
--- a/src/core/execution/SpawnExecution.ts
+++ b/src/core/execution/SpawnExecution.ts
@@ -25,28 +25,25 @@ export class SpawnExecution implements Execution {
return;
}
- const existing = this.mg
- .players()
- .find((p) => p.id() == this.playerInfo.id);
- if (existing) {
- existing.tiles().forEach((t) => existing.relinquish(t));
- getSpawnTiles(this.mg, this.tile).forEach((t) => {
- existing.conquer(t);
- });
- return;
+ let player: Player = null;
+ if (this.mg.hasPlayer(this.playerInfo.id)) {
+ player = this.mg.player(this.playerInfo.id);
+ } else {
+ player = this.mg.addPlayer(this.playerInfo);
}
- const player = this.mg.addPlayer(
- this.playerInfo,
- this.mg.config().startManpower(this.playerInfo),
- );
+ player.tiles().forEach((t) => player.relinquish(t));
getSpawnTiles(this.mg, this.tile).forEach((t) => {
player.conquer(t);
});
- this.mg.addExecution(new PlayerExecution(player.id()));
- if (player.type() == PlayerType.Bot) {
- this.mg.addExecution(new BotExecution(player));
+
+ if (!player.hasSpawned()) {
+ this.mg.addExecution(new PlayerExecution(player.id()));
+ if (player.type() == PlayerType.Bot) {
+ this.mg.addExecution(new BotExecution(player));
+ }
}
+ player.setHasSpawned(true);
}
owner(): Player {
diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts
index 34e0f6a56..99663faad 100644
--- a/src/core/execution/TradeShipExecution.ts
+++ b/src/core/execution/TradeShipExecution.ts
@@ -111,6 +111,9 @@ export class TradeShipExecution implements Execution {
break;
case PathFindResultType.PathNotFound:
consolex.warn("captured trade ship cannot find route");
+ if (this.tradeShip.isActive()) {
+ this.tradeShip.delete(false);
+ }
this.active = false;
break;
}
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 1c316ed5b..5dd3087dc 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -60,7 +60,7 @@ export enum GameMapType {
Australia = "Australia",
Iceland = "Iceland",
Japan = "Japan",
- TwoSeas = "Between Two Seas",
+ BetweenTwoSeas = "Between Two Seas",
KnownWorld = "Known World",
}
@@ -316,6 +316,9 @@ export interface Player {
largestClusterBoundingBox: { min: Cell; max: Cell } | null;
lastTileChange(): Tick;
+ hasSpawned(): boolean;
+ setHasSpawned(hasSpawned: boolean): void;
+
// Territory
tiles(): ReadonlySet;
borderTiles(): ReadonlySet;
@@ -430,7 +433,7 @@ export interface Game extends GameMap {
playerByClientID(id: ClientID): Player | null;
playerBySmallID(id: number): Player | TerraNullius;
hasPlayer(id: PlayerID): boolean;
- addPlayer(playerInfo: PlayerInfo, manpower: number): Player;
+ addPlayer(playerInfo: PlayerInfo): Player;
terraNullius(): TerraNullius;
owner(ref: TileRef): Player | TerraNullius;
diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts
index fd0bb9301..bd13ceeac 100644
--- a/src/core/game/GameImpl.ts
+++ b/src/core/game/GameImpl.ts
@@ -107,7 +107,7 @@ export class GameImpl implements Game {
private addHumans() {
if (this.config().gameConfig().gameMode != GameMode.Team) {
- this._humans.forEach((p) => this.addPlayer(p, 0));
+ this._humans.forEach((p) => this.addPlayer(p));
return;
}
const playerToTeam = assignTeams(this._humans);
@@ -116,7 +116,7 @@ export class GameImpl implements Game {
console.warn(`Player ${playerInfo.name} was kicked from team`);
continue;
}
- this.addPlayer(playerInfo, 0, team);
+ this.addPlayer(playerInfo, team);
}
}
@@ -346,16 +346,12 @@ export class GameImpl implements Game {
return this.player(id);
}
- addPlayer(
- playerInfo: PlayerInfo,
- manpower: number,
- team: Team = null,
- ): Player {
+ addPlayer(playerInfo: PlayerInfo, team: Team = null): Player {
const player = new PlayerImpl(
this,
this.nextPlayerID,
playerInfo,
- manpower,
+ this.config().startManpower(playerInfo),
team ?? this.maybeAssignTeam(playerInfo),
);
this._playersBySmallID.push(player);
diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts
index 5bb7c5693..044a2327e 100644
--- a/src/core/game/GameUpdates.ts
+++ b/src/core/game/GameUpdates.ts
@@ -111,6 +111,7 @@ export interface PlayerUpdate {
incomingAttacks: AttackUpdate[];
outgoingAllianceRequests: PlayerID[];
stats: PlayerStats;
+ hasSpawned: boolean;
}
export interface AllianceRequestUpdate {
diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts
index 430a24cb4..6bd58eb79 100644
--- a/src/core/game/GameView.ts
+++ b/src/core/game/GameView.ts
@@ -266,6 +266,9 @@ export class PlayerView {
stats(): PlayerStats {
return this.data.stats;
}
+ hasSpawned(): boolean {
+ return this.data.hasSpawned;
+ }
}
export class GameView implements GameMap {
diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts
index 85878ed77..0f8ffc0f0 100644
--- a/src/core/game/PlayerImpl.ts
+++ b/src/core/game/PlayerImpl.ts
@@ -94,12 +94,14 @@ export class PlayerImpl implements Player {
public _outgoingAttacks: Attack[] = [];
public _outgoingLandAttacks: Attack[] = [];
+ private _hasSpawned = false;
+
constructor(
private mg: GameImpl,
private _smallID: number,
private readonly playerInfo: PlayerInfo,
startTroops: number,
- private _team: Team | null,
+ private readonly _team: Team | null,
) {
this._flag = playerInfo.flag;
this._name = sanitizeUsername(playerInfo.name);
@@ -162,6 +164,7 @@ export class PlayerImpl implements Player {
),
outgoingAllianceRequests: outgoingAllianceRequests,
stats: this.mg.stats().getPlayerStats(this.id()),
+ hasSpawned: this.hasSpawned(),
};
}
@@ -291,6 +294,14 @@ export class PlayerImpl implements Player {
return this._tiles.size > 0;
}
+ hasSpawned(): boolean {
+ return this._hasSpawned;
+ }
+
+ setHasSpawned(hasSpawned: boolean): void {
+ this._hasSpawned = hasSpawned;
+ }
+
incomingAllianceRequests(): AllianceRequest[] {
return this.mg.allianceRequests.filter((ar) => ar.recipient() == this);
}
diff --git a/src/core/game/TerrainMapFileLoader.ts b/src/core/game/TerrainMapFileLoader.ts
index db730de11..636709968 100644
--- a/src/core/game/TerrainMapFileLoader.ts
+++ b/src/core/game/TerrainMapFileLoader.ts
@@ -39,7 +39,7 @@ const MAP_FILE_NAMES: Record = {
[GameMapType.Australia]: "Australia",
[GameMapType.Iceland]: "Iceland",
[GameMapType.Japan]: "Japan",
- [GameMapType.TwoSeas]: "TwoSeas",
+ [GameMapType.BetweenTwoSeas]: "BetweenTwoSeas",
[GameMapType.KnownWorld]: "KnownWorld",
};
diff --git a/src/scripts/generateTerrainMaps.ts b/src/scripts/generateTerrainMaps.ts
index a63dae382..7f83de7d1 100644
--- a/src/scripts/generateTerrainMaps.ts
+++ b/src/scripts/generateTerrainMaps.ts
@@ -19,7 +19,7 @@ const maps = [
"Australia",
"Pangaea",
"Iceland",
- "TwoSeas",
+ "BetweenTwoSeas",
"Japan",
"KnownWorld",
];
diff --git a/src/server/Archive.ts b/src/server/Archive.ts
index b9f280879..b466fd929 100644
--- a/src/server/Archive.ts
+++ b/src/server/Archive.ts
@@ -10,10 +10,10 @@ const log = logger.child({ component: "Archive" });
// R2 client configuration
const r2 = new S3({
region: "auto", // R2 ignores region, but it's required by the SDK
- endpoint: config.r2Endpoint(), // You'll need to add this to your config
+ endpoint: config.r2Endpoint(),
credentials: {
- accessKeyId: config.r2AccessKey(), // You'll need to add these
- secretAccessKey: config.r2SecretKey(), // credential methods to your config
+ accessKeyId: config.r2AccessKey(),
+ secretAccessKey: config.r2SecretKey(),
},
});
diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts
index bf3db8566..54cf9a329 100644
--- a/src/server/GameServer.ts
+++ b/src/server/GameServer.ts
@@ -369,8 +369,8 @@ export class GameServer {
this.turns,
this._startTime,
Date.now(),
- this.winner.winner,
- this.winner.winnerType,
+ this.winner?.winner,
+ this.winner?.winnerType,
this.allPlayersStats,
),
);
diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts
index 488d48aea..5fdd442f8 100644
--- a/src/server/MapPlaylist.ts
+++ b/src/server/MapPlaylist.ts
@@ -88,7 +88,7 @@ export class MapPlaylist {
Pangaea: 1,
Asia: 1,
Mars: 1,
- TwoSeas: 3,
+ BetweenTwoSeas: 3,
Japan: 3,
BlackSea: 1,
};
diff --git a/tests/Attack.test.ts b/tests/Attack.test.ts
index 1fa3d1b41..f9c46bcc3 100644
--- a/tests/Attack.test.ts
+++ b/tests/Attack.test.ts
@@ -39,7 +39,7 @@ describe("Attack", () => {
null,
"attacker_id",
);
- game.addPlayer(attackerInfo, 1000);
+ game.addPlayer(attackerInfo);
const defenderInfo = new PlayerInfo(
"us",
"defender dude",
@@ -47,7 +47,7 @@ describe("Attack", () => {
null,
"defender_id",
);
- game.addPlayer(defenderInfo, 1000);
+ game.addPlayer(defenderInfo);
defenderSpawn = game.ref(0, 15);
attackerSpawn = game.ref(0, 10);
diff --git a/tests/MissileSilo.test.ts b/tests/MissileSilo.test.ts
index 1c7dd44b3..e8e926b3d 100644
--- a/tests/MissileSilo.test.ts
+++ b/tests/MissileSilo.test.ts
@@ -38,7 +38,7 @@ describe("MissileSilo", () => {
null,
"attacker_id",
);
- game.addPlayer(attacker_info, 1000);
+ game.addPlayer(attacker_info);
game.addExecution(
new SpawnExecution(game.player(attacker_info.id).info(), game.ref(1, 1)),
diff --git a/tests/SAM.test.ts b/tests/SAM.test.ts
index 83a59ef4f..4f63f9274 100644
--- a/tests/SAM.test.ts
+++ b/tests/SAM.test.ts
@@ -50,8 +50,8 @@ describe("SAM", () => {
null,
"attacker_id",
);
- game.addPlayer(defender_info, 1000);
- game.addPlayer(attacker_info, 1000);
+ game.addPlayer(defender_info);
+ game.addPlayer(attacker_info);
game.addExecution(
new SpawnExecution(game.player(defender_info.id).info(), game.ref(1, 1)),
diff --git a/tests/TerritoryCapture.test.ts b/tests/TerritoryCapture.test.ts
index a32aeb6b7..aee96aec6 100644
--- a/tests/TerritoryCapture.test.ts
+++ b/tests/TerritoryCapture.test.ts
@@ -7,7 +7,6 @@ describe("Territory management", () => {
const game = await setup("Plains");
game.addPlayer(
new PlayerInfo("us", "test_player", PlayerType.Human, null, "test_id"),
- 1000,
);
const spawnTile = game.map().ref(50, 50);
game.addExecution(
diff --git a/tests/Warship.test.ts b/tests/Warship.test.ts
index e7adcd47e..c1ca61ecf 100644
--- a/tests/Warship.test.ts
+++ b/tests/Warship.test.ts
@@ -27,7 +27,7 @@ describe("Warship", () => {
null,
"player_1_id",
);
- game.addPlayer(player_1_info, 1000);
+ game.addPlayer(player_1_info);
const player_2_info = new PlayerInfo(
"us",
"boat dude",
@@ -35,7 +35,7 @@ describe("Warship", () => {
null,
"player_2_id",
);
- game.addPlayer(player_2_info, 1000);
+ game.addPlayer(player_2_info);
game.addExecution(
new SpawnExecution(