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(