Files
OpenFrontIO/src/client/ClientGame.ts
T
2024-08-11 13:47:10 -07:00

247 lines
8.0 KiB
TypeScript

import {Executor} from "../core/execution/Executor";
import {Cell, ClientID, MutableGame, LobbyID, PlayerEvent, PlayerID, PlayerInfo, MutablePlayer, TerrainMap, TileEvent, Player, Game, BoatEvent} from "../core/Game";
import {createGame} from "../core/GameImpl";
import {Ticker, TickEvent} from "../core/Ticker";
import {EventBus} from "../core/EventBus";
import {Settings} from "../core/Settings";
import {GameRenderer} from "./graphics/GameRenderer";
import {InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent} from "./InputHandler"
import {ClientIntentMessageSchema, ClientJoinMessageSchema, ClientMessageSchema, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn} from "../core/Schemas";
export function createClientGame(name: string, clientID: ClientID, lobbyID: LobbyID, settings: Settings, terrainMap: TerrainMap): ClientGame {
let eventBus = new EventBus()
let gs = createGame(terrainMap, eventBus)
let gameRenderer = new GameRenderer(gs, settings.theme(), document.createElement("canvas"))
let ticker = new Ticker(settings.tickIntervalMs(), eventBus)
return new ClientGame(
name,
clientID,
lobbyID,
ticker,
eventBus,
gs,
gameRenderer,
new InputHandler(eventBus),
new Executor(gs)
)
}
export class ClientGame {
private myPlayer: Player
private turns: Turn[] = []
private socket: WebSocket
private isActive = false
private ticksPerTurn = 1
private ticksThisTurn = 0
private currTurn = 0
private spawned = false
private intervalID: NodeJS.Timeout
constructor(
private playerName: string,
private id: ClientID,
private gameID: LobbyID,
private ticker: Ticker,
private eventBus: EventBus,
private gs: Game,
private renderer: GameRenderer,
private input: InputHandler,
private executor: Executor
) { }
public joinLobby() {
this.socket = new WebSocket(`ws://localhost:3000`)
this.socket.onopen = () => {
console.log('Connected to game server!');
this.socket.send(
JSON.stringify(
ClientJoinMessageSchema.parse({
type: "join",
lobbyID: this.gameID,
clientID: this.id
})
)
)
};
this.socket.onmessage = (event: MessageEvent) => {
const message: ServerMessage = ServerMessageSchema.parse(JSON.parse(event.data))
if (message.type == "start") {
console.log("starting game!")
this.start()
}
if (message.type == "turn") {
if (message.turn.intents)
this.addTurn(message.turn)
}
};
}
public start() {
this.isActive = true
console.log('starting game!')
// TODO: make each class do this, or maybe have client intercept all requests?
//this.eventBus.on(TickEvent, (e) => this.tick(e))
this.eventBus.on(TileEvent, (e) => this.renderer.tileUpdate(e))
this.eventBus.on(PlayerEvent, (e) => this.playerEvent(e))
this.eventBus.on(BoatEvent, (e) => this.renderer.boatEvent(e))
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e))
this.eventBus.on(ZoomEvent, (e) => this.renderer.onZoom(e))
this.eventBus.on(DragEvent, (e) => this.renderer.onMove(e))
this.renderer.initialize()
this.input.initialize()
this.executor.spawnBots(1000)
this.intervalID = setInterval(() => this.tick(), 10);
}
public stop() {
clearInterval(this.intervalID)
this.isActive = false
}
public addTurn(turn: Turn): void {
this.turns.push(turn)
}
public tick() {
if (this.ticksThisTurn >= this.ticksPerTurn) {
if (this.currTurn >= this.turns.length) {
return
}
this.executor.addTurn(this.turns[this.currTurn])
this.currTurn++
this.ticksThisTurn = 0
}
this.ticksThisTurn++
console.log('client ticking')
this.gs.tick()
this.renderer.tick()
}
private playerEvent(event: PlayerEvent) {
console.log('received new player event!')
// TODO: what if multiple players has same name
if (event.player.info().name == this.playerName) {
console.log('setting name')
this.myPlayer = event.player
}
this.renderer.playerEvent(event)
}
private inputEvent(event: MouseDownEvent) {
if (!this.isActive) {
return
}
const cell = this.renderer.screenToWorldCoordinates(event.x, event.y)
if (!this.gs.isOnMap(cell)) {
return
}
const tile = this.gs.tile(cell)
if (!tile.hasOwner() && !this.spawned && this.myPlayer == null) {
this.sendSpawnIntent(cell)
this.spawned = true
return
}
if (!this.spawned || this.myPlayer == null) {
return
}
const owner = tile.owner()
const targetID = owner.isPlayer() ? owner.id() : null
if (tile.owner() != this.myPlayer) {
if (this.myPlayer.sharesBorderWith(tile.owner())) {
this.sendAttackIntent(targetID, cell)
} else {
// TODO verify on ocean
console.log('going to send boat')
this.sendBoatAttackIntent(targetID, cell)
}
}
}
private sendSpawnIntent(cell: Cell) {
const spawn = JSON.stringify(
ClientIntentMessageSchema.parse({
type: "intent",
clientID: this.id,
gameID: this.gameID,
intent: {
type: "spawn",
name: this.playerName,
isBot: false,
x: cell.x,
y: cell.y
}
})
)
console.log(spawn)
if (this.socket.readyState === WebSocket.OPEN) {
console.log(`seding spawn intent: ${spawn}`)
this.socket.send(spawn)
} else {
console.log('WebSocket is not open. Current state:', this.socket.readyState);
}
}
private sendAttackIntent(targetID: PlayerID, cell: Cell) {
const attack = JSON.stringify(
ClientIntentMessageSchema.parse({
type: "intent",
clientID: this.id,
gameID: this.gameID,
intent: {
type: "attack",
attackerID: this.myPlayer.id(),
targetID: targetID,
troops: 2000,
targetX: cell.x,
targetY: cell.y
}
})
)
console.log(attack)
if (this.socket.readyState === WebSocket.OPEN) {
console.log(`sending attack intent: ${attack}`)
this.socket.send(attack)
} else {
console.log('WebSocket is not open. Current state:', this.socket.readyState);
}
}
private sendBoatAttackIntent(targetID: PlayerID, cell: Cell) {
const attack = JSON.stringify(
ClientIntentMessageSchema.parse({
type: "intent",
clientID: this.id,
gameID: this.gameID,
intent: {
type: "boat",
attackerID: this.myPlayer.id(),
targetID: targetID,
troops: 2000,
x: cell.x,
y: cell.y,
}
})
)
console.log(attack)
if (this.socket.readyState === WebSocket.OPEN) {
console.log(`sending boat attack intent: ${attack}`)
this.socket.send(attack)
} else {
console.log('WebSocket is not open. Current state:', this.socket.readyState);
}
}
}