split Player/Unit from its Views for better separation

This commit is contained in:
evanpelle
2025-01-23 11:03:02 -08:00
committed by Evan
parent 96ab7e7f11
commit 4034d11015
10 changed files with 52 additions and 98 deletions
+10 -9
View File
@@ -9,6 +9,7 @@ import { UsernameInput } from "./UsernameInput";
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { PlayerView } from "../core/GameView"
export class PauseGameEvent implements GameEvent {
constructor(public readonly paused: boolean) { }
@@ -16,23 +17,23 @@ export class PauseGameEvent implements GameEvent {
export class SendAllianceRequestIntentEvent implements GameEvent {
constructor(
public readonly requestor: Player,
public readonly recipient: Player
public readonly requestor: PlayerView,
public readonly recipient: PlayerView
) { }
}
export class SendBreakAllianceIntentEvent implements GameEvent {
constructor(
public readonly requestor: Player,
public readonly recipient: Player
public readonly requestor: PlayerView,
public readonly recipient: PlayerView
) { }
}
export class SendAllianceReplyIntentEvent implements GameEvent {
constructor(
// The original alliance requestor
public readonly requestor: Player,
public readonly recipient: Player,
public readonly requestor: PlayerView,
public readonly recipient: PlayerView,
public readonly accepted: boolean
) { }
}
@@ -73,15 +74,15 @@ export class SendTargetPlayerIntentEvent implements GameEvent {
export class SendEmojiIntentEvent implements GameEvent {
constructor(
public readonly recipient: Player | typeof AllPlayers,
public readonly recipient: PlayerView | typeof AllPlayers,
public readonly emoji: string
) { }
}
export class SendDonateIntentEvent implements GameEvent {
constructor(
public readonly sender: Player,
public readonly recipient: Player,
public readonly sender: PlayerView,
public readonly recipient: PlayerView,
public readonly troops: number | null,
) { }
}
+5 -5
View File
@@ -31,14 +31,14 @@ export class NameLayer implements Layer {
private renderRefreshRate = 500
private rand = new PseudoRandom(10)
private renders: RenderInfo[] = []
private seenPlayers: Set<Player> = new Set()
private seenPlayers: Set<PlayerView> = new Set()
private traitorIconImage: HTMLImageElement;
private allianceIconImage: HTMLImageElement;
private targetIconImage: HTMLImageElement;
private crownIconImage: HTMLImageElement;
private container: HTMLDivElement
private myPlayer: Player | null = null
private firstPlace: Player | null = null
private myPlayer: PlayerView | null = null
private firstPlace: PlayerView | null = null
constructor(private game: GameView, private theme: Theme, private transformHandler: TransformHandler, private clientID: ClientID) {
this.traitorIconImage = new Image();
@@ -115,7 +115,7 @@ export class NameLayer implements Layer {
)
}
private createPlayerElement(player: Player): HTMLDivElement {
private createPlayerElement(player: PlayerView): HTMLDivElement {
const element = document.createElement('div')
element.style.position = 'absolute'
element.style.display = 'flex'
@@ -281,7 +281,7 @@ export class NameLayer implements Layer {
return icon
}
private getPlayer(): Player | null {
private getPlayer(): PlayerView | null {
if (this.myPlayer != null) {
return this.myPlayer
}
@@ -6,7 +6,7 @@ import { ClientID } from '../../../core/Schemas';
import { EventBus } from '../../../core/EventBus';
import { TransformHandler } from '../TransformHandler';
import { MouseMoveEvent } from '../../InputHandler';
import { GameView, PlayerView } from '../../../core/GameView';
import { GameView, PlayerView, UnitView } from '../../../core/GameView';
import { TileRef } from '../../../core/game/GameMap';
import { PauseGameEvent } from '../../Transport';
import { renderNumber, renderTroops } from '../../Utils';
@@ -20,7 +20,7 @@ function euclideanDistWorld(coord: { x: number, y: number }, tileRef: TileRef, g
}
function distSortUnitWorld(coord: { x: number, y: number }, game: GameView) {
return (a: Unit, b: Unit) => {
return (a: Unit | UnitView, b: Unit | UnitView) => {
const distA = euclideanDistWorld(coord, a.tile(), game);
const distB = euclideanDistWorld(coord, b.tile(), game);
return distA - distB;
@@ -42,13 +42,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
public transform!: TransformHandler;
@state()
private player: Player | null = null;
private player: PlayerView | null = null;
@state()
private playerProfile: PlayerProfile | null = null;
@state()
private unit: Unit | null = null;
private unit: UnitView | null = null;
@state()
private showPauseButton: boolean = true;
@@ -92,8 +92,8 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
const owner = this.game.owner(tile);
if (owner && owner.isPlayer()) {
this.player = owner;
(this.player as PlayerView).profile().then(p => {
this.player = owner as PlayerView;
this.player.profile().then(p => {
this.playerProfile = p;
});
this.setVisible(true);
@@ -135,14 +135,14 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
this.requestUpdate();
}
private myPlayer(): Player | null {
private myPlayer(): PlayerView | null {
if (!this.game) {
return null;
}
return this.game.playerByClientID(this.clientID);
}
private renderPlayerInfo(player: Player) {
private renderPlayerInfo(player: PlayerView) {
const myPlayer = this.myPlayer();
const isAlly = myPlayer?.isAlliedWith(player)
let relationHtml = null;
@@ -181,7 +181,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
`;
}
private renderUnitInfo(unit: Unit) {
private renderUnitInfo(unit: UnitView) {
const isAlly = (unit.owner() == this.myPlayer() || this.myPlayer()?.isAlliedWith(unit.owner())) ?? false;
return html`
<div class="info-content">
+3 -3
View File
@@ -7,7 +7,7 @@ import anchorIcon from '../../../../resources/images/AnchorIcon.png';
import missileSiloIcon from '../../../../resources/images/MissileSiloUnit.png';
import shieldIcon from '../../../../resources/images/ShieldIcon.png';
import cityIcon from '../../../../resources/images/CityIcon.png';
import { GameView } from "../../../core/GameView";
import { GameView, UnitView } from "../../../core/GameView";
import { Cell, GameUpdateType, Unit, UnitType } from "../../../core/game/Game";
import { euclDistFN } from "../../../core/game/GameMap";
@@ -109,7 +109,7 @@ export class StructureLayer implements Layer {
return unitType in this.unitConfigs;
}
private handleUnitRendering(unit: Unit) {
private handleUnitRendering(unit: UnitView) {
const unitType = unit.type();
if (!this.isUnitTypeSupported(unitType)) return;
@@ -157,7 +157,7 @@ export class StructureLayer implements Layer {
startY: number,
width: number,
height: number,
unit: Unit
unit: UnitView
) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
+12 -12
View File
@@ -5,7 +5,7 @@ import { Layer } from "./Layer";
import { EventBus } from "../../../core/EventBus";
import { AlternateViewEvent } from "../../InputHandler";
import { ClientID } from "../../../core/Schemas";
import { GameView } from "../../../core/GameView";
import { GameView, PlayerView, UnitView } from "../../../core/GameView";
import { euclDistFN, manhattanDistFN, TileRef } from "../../../core/game/GameMap";
enum Relationship {
@@ -18,15 +18,15 @@ export class UnitLayer implements Layer {
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
private boatToTrail = new Map<Unit, Set<TileRef>>();
private boatToTrail = new Map<UnitView, Set<TileRef>>();
private theme: Theme = null;
private alternateView = false;
private myPlayer: Player | null = null;
private myPlayer: PlayerView | null = null;
private oldShellTile = new Map<Unit, TileRef>();
private oldShellTile = new Map<UnitView, TileRef>();
constructor(private game: GameView, private eventBus: EventBus, private clientID: ClientID) {
this.theme = game.config().theme();
@@ -77,7 +77,7 @@ export class UnitLayer implements Layer {
}
}
private relationship(unit: Unit): Relationship {
private relationship(unit: UnitView): Relationship {
if (this.myPlayer == null) {
return Relationship.Enemy;
}
@@ -90,7 +90,7 @@ export class UnitLayer implements Layer {
return Relationship.Enemy;
}
onUnitEvent(unit: Unit) {
onUnitEvent(unit: UnitView) {
switch (unit.type()) {
case UnitType.TransportShip:
this.handleBoatEvent(unit);
@@ -114,7 +114,7 @@ export class UnitLayer implements Layer {
}
}
private handleDestroyerEvent(unit: Unit) {
private handleDestroyerEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
@@ -149,7 +149,7 @@ export class UnitLayer implements Layer {
}
}
private handleBattleshipEvent(unit: Unit) {
private handleBattleshipEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
@@ -195,7 +195,7 @@ export class UnitLayer implements Layer {
}
}
private handleShellEvent(unit: Unit) {
private handleShellEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear current and previous positions
@@ -227,7 +227,7 @@ export class UnitLayer implements Layer {
);
}
private handleNuke(unit: Unit) {
private handleNuke(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
@@ -249,7 +249,7 @@ export class UnitLayer implements Layer {
}
}
private handleTradeShipEvent(unit: Unit) {
private handleTradeShipEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
@@ -282,7 +282,7 @@ export class UnitLayer implements Layer {
}
}
private handleBoatEvent(unit: Unit) {
private handleBoatEvent(unit: UnitView) {
const rel = this.relationship(unit);
if (!this.boatToTrail.has(unit)) {
@@ -38,7 +38,7 @@ const buildTable: BuildItemDisplay[][] = [
export class BuildMenu extends LitElement {
public game: GameView;
public eventBus: EventBus;
private myPlayer: Player;
private myPlayer: PlayerView;
private clickedCell: Cell;
private playerActions: PlayerActions | null
@@ -263,7 +263,7 @@ export class RadialMenu implements Layer {
const canSendEmojiToAllPlayers = this.g.ownerID(tile) == myPlayer.smallID() && actions.canSendEmojiAllPlayers
if (canSendEmojiToPlayer || canSendEmojiToAllPlayers) {
this.activateMenuElement(Slot.Emoji, "#00a6a4", emojiIcon, () => {
const target = this.g.owner(tile) == myPlayer ? AllPlayers : (this.g.owner(tile) as Player)
const target = this.g.owner(tile) == myPlayer ? AllPlayers : (this.g.owner(tile) as PlayerView)
this.emojiTable.onEmojiClicked = (emoji: string) => {
this.emojiTable.hideTable()
this.eventBus.emit(new SendEmojiIntentEvent(target, emoji))
@@ -290,7 +290,7 @@ export class RadialMenu implements Layer {
if (!this.g.hasOwner(tile)) {
return
}
const other = this.g.owner(tile) as Player
const other = this.g.owner(tile) as PlayerView
if (actions?.interaction.canDonate) {
+4 -53
View File
@@ -6,7 +6,7 @@ import { TerraNulliusImpl } from './game/TerraNulliusImpl';
import { WorkerClient } from './worker/WorkerClient';
import { GameMap, GameMapImpl, TileRef, TileUpdate } from './game/GameMap';
export class UnitView implements Unit {
export class UnitView {
public _wasUpdated = true
public lastPos: MapPos[] = []
@@ -62,12 +62,9 @@ export class UnitView implements Unit {
}
}
export class PlayerView implements Player {
export class PlayerView {
constructor(private game: GameView, public data: PlayerUpdate, public nameData: NameViewData) { }
borderTiles(): ReadonlySet<TileRef> {
throw new Error('Method not implemented.');
}
async actions(tile: TileRef): Promise<PlayerActions> {
return this.game.worker.playerInteraction(this.id(), this.game.x(tile), this.game.y(tile))
@@ -80,9 +77,6 @@ export class PlayerView implements Player {
smallID(): number {
return this.data.smallID
}
lastTileChange(): Tick {
return 0
}
name(): string {
return this.data.name
}
@@ -129,67 +123,24 @@ export class PlayerView implements Player {
return this.data.troops
}
isAlliedWith(other: Player): boolean {
isAlliedWith(other: PlayerView): boolean {
return this.data.allies.some(n => other.smallID() == n)
}
allianceWith(other: Player): Alliance | null {
return null
}
units(...types: UnitType[]): Unit[] {
return []
}
sharesBorderWith(other: Player | TerraNullius): boolean {
return false
}
incomingAllianceRequests(): AllianceRequest[] {
return []
}
outgoingAllianceRequests(): AllianceRequest[] {
return []
}
alliances(): Alliance[] {
return []
}
recentOrPendingAllianceRequestWith(other: Player): boolean {
return false
}
relation(other: Player): Relation {
return Relation.Neutral
}
profile(): Promise<PlayerProfile> {
return this.game.worker.playerProfile(this.smallID())
}
allRelationsSorted(): { player: Player; relation: Relation; }[] {
return []
}
transitiveTargets(): Player[] {
transitiveTargets(): PlayerView[] {
return [...this.targets(), ...this.allies().flatMap(p => p.targets())]
}
isTraitor(): boolean {
return this.data.isTraitor
}
canTarget(other: Player): boolean {
return false
}
toString(): string {
return ''
}
canSendEmoji(recipient: Player | typeof AllPlayers): boolean {
return false
}
outgoingEmojis(): EmojiMessage[] {
return this.data.outgoingEmojis
}
canDonate(recipient: Player): boolean {
return false
}
canBuild(type: UnitType, targetTile: TileRef): TileRef | false {
return false
}
info(): PlayerInfo {
return new PlayerInfo(this.name(), this.type(), this.clientID(), this.id())
}
+4 -3
View File
@@ -7,6 +7,7 @@ import { GameConfig } from "../Schemas";
import { DefaultConfig } from "./DefaultConfig";
import { DevConfig, DevServerConfig } from "./DevConfig";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../GameView";
export enum GameEnv {
Dev,
@@ -58,8 +59,8 @@ export interface Config {
numSpawnPhaseTurns(): number
startManpower(playerInfo: PlayerInfo): number
populationIncreaseRate(player: Player): number
goldAdditionRate(player: Player): number
populationIncreaseRate(player: Player | PlayerView): number
goldAdditionRate(player: Player | PlayerView): number
troopAdjustmentRate(player: Player): number
attackTilesPerTick(attckTroops: number, attacker: Player, defender: Player | TerraNullius, numAdjacentTilesWithEnemy: number): number
attackLogic(gm: GameMap, attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: TileRef): {
@@ -68,7 +69,7 @@ export interface Config {
tilesPerTickUsed: number
}
attackAmount(attacker: Player, defender: Player | TerraNullius): number
maxPopulation(player: Player): number
maxPopulation(player: Player | PlayerView): number
cityPopulationIncrease(): number
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number
boatMaxDistance(): number
+2 -1
View File
@@ -1,5 +1,6 @@
import { Config } from "../configuration/Config"
import { GameEvent } from "../EventBus"
import { PlayerView } from "../GameView"
import { ClientID, GameConfig, GameID } from "../Schemas"
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap"
@@ -46,7 +47,7 @@ export enum GameType {
}
export interface UnitInfo {
cost: (player: Player) => Gold
cost: (player: Player | PlayerView) => Gold
// Determines if its owner changes when its tile is conquered.
territoryBound: boolean
maxHealth?: number,