diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 66c09fcab..ff7f393d4 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -75,7 +75,7 @@ export async function createClientGame(lobbyConfig: LobbyConfig, gameConfig: Gam const gameMap = await loadTerrainMap(gameConfig.gameMap); const worker = new WorkerClient(lobbyConfig.gameID, gameConfig) await worker.initialize() - const gameView = new GameView(worker, config, gameMap.gameMap) + const gameView = new GameView(worker, config, gameMap.gameMap, lobbyConfig.clientID) consolex.log('going to init path finder') diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 2297bd0f9..33c842d8b 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -80,7 +80,6 @@ export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventB if (!(playerInfo instanceof WinModal)) { console.error('win modal not found') } - winModel.clientID = clientID winModel.game = game diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index 04a2e9f8e..0d8726050 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -5,16 +5,160 @@ import { ClientID } from '../../../core/Schemas'; import { GameView, PlayerView } from '../../../core/game/GameView'; import { Layer } from './Layer'; import { GameUpdateType } from '../../../core/game/GameUpdates'; +import { PseudoRandom } from '../../../core/PseudoRandom'; +import { simpleHash } from '../../../core/Util'; + + +const lowRadiationVictoryQuotes = [ + "Victory is mine. The world endures - under new management.", + "They thought they could stop me. Now they serve me.", + "The old order has fallen. My reign begins.", + "Not every victory requires destruction.", + "From this day forward, all will know who rules.", + "The war is over. Long live the victor.", + "A new empire rises - with me at its helm.", + "The throne is claimed. The crown is mine.", + "Today marks the beginning of my dynasty.", + "They feared my wrath. Now they'll know my rule.", + "Victory was inevitable. Surrender was optional.", + "A new era dawns - under my command.", + "The pieces are in place. The victory is complete.", + "Let history remember who conquered all.", + "Their resistance only delayed the inevitable.", + "Power shifts. Empires fall. I remain.", + "The world has a new master now.", + "Their armies fell. Their nations surrendered. I prevailed.", + "All paths led to this victory.", + "The world bows to its new ruler.", + "From the ashes of their defeat, my victory rises.", + "Destiny called. I answered. The world followed.", + "They called me tyrant. Now they call me emperor.", + "The old powers have fallen. Mine endures.", + "Every empire needs a beginning. This is mine.", + "Their defiance crumbled before my ambition.", + "No more rebels. No more resistance. Only order.", + "The final piece falls into place.", + "Let them write of this day in their histories.", + "My vision becomes reality.", + "Their surrender was wise. My victory was certain.", + "The wheels of fate turn in my favor.", + "A new chapter begins - written by the victor.", + "They fought the inevitable. The inevitable won.", + "All that was theirs is now mine.", + "The gods themselves bow before my triumph.", + "Their kingdoms shatter. My empire rises.", + "Victory tastes sweeter than wine.", + "The crown suits me well, don't you think?", + "Behold the dawn of my eternal reign." +]; + +const highRadiationVictoryQuotes = [ + "Let the world burn. I just want to rule the ashes.", + "The old world died screaming. My new one rises from its bones.", + "They could have surrendered. Now they glow.", + "Everything burns. The throne of ashes awaits.", + "A wasteland needs a king. I have answered the call.", + "Their cities turned to glass. My victory endures.", + "Who needs a pristine world when you can rule its ruins?", + "They feared the fire. I embraced it.", + "The radiation clears the way for my reign.", + "A dead world makes for quiet subjects.", + "From atomic fire, my kingdom rises.", + "Nothing left but ashes and victory.", + "They wanted war. I gave them annihilation.", + "The world burns green. My empire glows eternal.", + "All thrones are built on ashes. Mine just glows.", + "Look upon my works and despair - if you can still see.", + "The mushroom clouds herald my coronation.", + "A crown of thorns for a radioactive realm.", + "They chose extinction. I chose supremacy.", + "The wasteland's throne is mine to claim.", + "The Geiger counter clicks. The masses bow.", + "Atoms split. Nations fall. I reign supreme.", + "My kingdom radiates with possibility.", + "The isotopes of victory decay slowly.", + "Critical mass achieved. Dominion secured.", + "Chain reaction complete: world falls, empire rises.", + "In nuclear fire, I forge my legacy." +]; + +export const defeatQuotes = [ + // Last words and final thoughts + "The flame of our nation flickers out...", + "History will remember we fought to the last.", + "Our glory fades into darkness.", + "The end comes for all nations. Today, it comes for us.", + "We fought. We failed. We fade.", + "Our time in the sun is done.", + "The pages of history close on our chapter.", + "So falls the dream of empire.", + "We built in stone, but even stone crumbles.", + "The stars themselves will remember our defiance.", + + // Bitter defeats + "Treachery and fate conspired against us.", + "Our enemies dance on the graves of heroes.", + "The vultures circle what remains.", + "Let them celebrate. Dead men need no vengeance.", + "The light dies. The darkness wins.", + "Our walls fall. Our spirit breaks.", + "Victory goes to the ruthless.", + "Time claims another empire.", + "The crown shatters. The throne burns.", + "Our banners fall. Our story ends.", + + // Philosophical acceptance + "All great nations must face their sunset.", + "Time is the ultimate conqueror.", + "Today we join the ghosts of fallen empires.", + "What rises must also fall.", + "Our legacy scatters like dust in the wind.", + "The wheel turns. We descend.", + "From glory to ashes, as all things must.", + "The tides of fate show no mercy.", + "Let history judge if we were worthy.", + "We join the eternal silence.", + + // Defiant last stands + "Our spirit remains unbroken.", + "They may take our lands, but not our pride.", + "Remember us as we were, not as we fell.", + "We chose death before dishonor.", + "Our courage lives beyond our defeat.", + "Let them write of how we stood fast.", + "We fall, but we fall fighting.", + "Honor guides us to our end.", + "Death before surrender.", + "The echoes of our defiance will ring eternal.", + + // Prophetic/Cursed + "Our shadow will haunt their victory.", + "They'll learn the price of empire.", + "Time will prove our cause was just.", + "The seeds of their downfall are sown in our ashes.", + "Our fall heralds their doom.", + "Victory today. Nemesis tomorrow.", + "The wheel turns for all.", + "They'll remember us in their nightmares.", + "Our curse follows them to their graves.", + "What rises in our place will shake the world." +]; @customElement('win-modal') export class WinModal extends LitElement implements Layer { - public clientID: ClientID public game: GameView - private winner: PlayerView + + + private rand: PseudoRandom; + + private hasShownDeathModal = false @state() isVisible = false + private _title: string + private message: string + static styles = css` :host { display: block; @@ -114,25 +258,19 @@ export class WinModal extends LitElement implements Layer { `; render() { - if (!this.winner) return null; - const isWinner = this.winner.clientID() === this.clientID; - const title = isWinner ? 'You Won!!!' : 'You Lost!!!'; - const message = `${this.winner.name()} won the game!`; - return html` `; } - show(winner: PlayerView) { - this.winner = winner; + show() { this.isVisible = true; this.requestUpdate(); } @@ -147,15 +285,34 @@ export class WinModal extends LitElement implements Layer { window.location.reload(); } - private _handleContinue() { - this.hide(); + init() { + this.rand = new PseudoRandom(simpleHash(this.game.myClientID())) } - init() { } - tick() { + const myPlayer = this.game.myPlayer() + if (!this.hasShownDeathModal && myPlayer && !myPlayer.isAlive()) { + this.hasShownDeathModal = true + this._title = 'You died' + this.message = this.rand.randElement(defeatQuotes) + this.show() + } this.game.updatesSinceLastTick()[GameUpdateType.WinUpdate] - .forEach(wu => this.show(this.game.playerBySmallID(wu.winnerID) as PlayerView)) + .forEach(wu => { + const winner = this.game.playerBySmallID(wu.winnerID) as PlayerView + if (winner == this.game.myPlayer()) { + this._title = 'You Won!' + if (this.game.numTilesWithFallout() / this.game.numLandTiles() > .6) { + this.message = this.rand.randElement(highRadiationVictoryQuotes) + } else { + this.message = this.rand.randElement(lowRadiationVictoryQuotes) + } + } else { + this._title = `${winner.name()} has won!` + this.message = this.rand.randElement(defeatQuotes) + } + this.show() + }) } renderLayer(context: CanvasRenderingContext2D) { diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 07995f0bd..dab1368be 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -19,7 +19,7 @@ export class DevConfig extends DefaultConfig { } numSpawnPhaseTurns(): number { - return this.gameConfig().gameType == GameType.Singleplayer ? 40 : 200 + return this.gameConfig().gameType == GameType.Singleplayer ? 40 : 100 // return 100 } @@ -30,23 +30,23 @@ export class DevConfig extends DefaultConfig { return info } - // percentageTilesOwnedToWin(): number { - // return 10 - // } + percentageTilesOwnedToWin(): number { + return 1 + } - // populationIncreaseRate(player: Player): number { - // return this.maxPopulation(player) - // } + populationIncreaseRate(player: Player): number { + return this.maxPopulation(player) + } // boatMaxDistance(): number { // return 5000 // } - // numBots(): number { - // return 0 - // } - // spawnNPCs(): boolean { - // return false - // } + numBots(): number { + return 0 + } + spawnNPCs(): boolean { + return false + } } diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index f4d72495f..9d9ad325d 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -162,7 +162,15 @@ export class GameView implements GameMap { private _units = new Map() private updatedTiles: TileRef[] = [] - constructor(public worker: WorkerClient, private _config: Config, private _map: GameMap) { + + private _myPlayer: PlayerView | null = null + + constructor( + public worker: WorkerClient, + private _config: Config, + private _map: GameMap, + private _myClientID: ClientID + ) { this.lastUpdate = { tick: 0, packedTileUpdates: new BigUint64Array([]), @@ -210,6 +218,17 @@ export class GameView implements GameMap { return this.updatedTiles } + myClientID(): ClientID { + return this._myClientID + } + + myPlayer(): PlayerView | null { + if (this._myPlayer == null) { + this._myPlayer = this.playerByClientID(this._myClientID) + } + return this._myPlayer + } + player(id: PlayerID): PlayerView { if (this._players.has(id)) { return this._players.get(id)