diff --git a/src/lib/game/actions/baseAction.ts b/src/lib/game/actions/baseAction.ts deleted file mode 100644 index 78368322..00000000 --- a/src/lib/game/actions/baseAction.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { IGameAction } from '$lib/game/types' - -interface IActionOptions { - command: IGameAction['command'] - commandDescription: IGameAction['commandDescription'] -} - -export class BaseAction implements IGameAction { - public command: string - public commandDescription: string - - constructor({ command, commandDescription }: IActionOptions) { - this.command = command - this.commandDescription = commandDescription - } -} diff --git a/src/lib/game/actions/donateWoodToVillageAction.ts b/src/lib/game/actions/donateWoodToVillageAction.ts index 605e4ecc..799d3f13 100644 --- a/src/lib/game/actions/donateWoodToVillageAction.ts +++ b/src/lib/game/actions/donateWoodToVillageAction.ts @@ -1,32 +1,27 @@ -import { Village } from '../chunks' -import type { Warehouse } from '../objects/buildings/warehouse' -import { BaseAction } from './baseAction' -import { ANSWER } from '$lib/game/scenes/services/actionService' -import type { GameScene } from '$lib/game/types' - -interface IDonateWoodToVillageActionOptions { - scene: GameScene -} +import { ANSWER } from '$lib/game/services/actionService' +import type { Game, GameObjectPlayer } from '$lib/game/types' +import type { GameAction } from '$lib/game/actions/interface' -export class DonateWoodToVillageAction extends BaseAction { - scene: GameScene +interface DonateWoodToVillageActionOptions { + game: Game +} - constructor({ scene }: IDonateWoodToVillageActionOptions) { - super({ command: 'donate', commandDescription: '!donate [quantity]' }) +export class DonateWoodToVillageAction implements GameAction { + command = 'donate' + commandDescription = '!donate [quantity]' + game: Game - this.scene = scene + constructor({ game }: DonateWoodToVillageActionOptions) { + this.game = game } - async live(player, params) { - const amount = this.scene.actionService.getAmountFromChatCommand(params[0]) + async live(player: GameObjectPlayer, params: string[]) { + const amount = this.game.actionService.getAmountFromChatCommand(params[0]) if (!amount) { return ANSWER.WRONG_AMOUNT_ERROR } - let warehouse: Warehouse | undefined - if (this.scene.chunkNow instanceof Village) { - warehouse = this.scene.chunkNow.getWarehouse() - } + const warehouse = this.game.chunkService.chunk?.warehouse const isSuccess = await player.inventory.reduceOrDestroyItem('WOOD', amount) if (!isSuccess) { diff --git a/src/lib/game/actions/interface.ts b/src/lib/game/actions/interface.ts new file mode 100644 index 00000000..3ec94365 --- /dev/null +++ b/src/lib/game/actions/interface.ts @@ -0,0 +1,10 @@ +import type { GameObjectPlayer, IGameActionResponse } from '$lib/game/types' + +export interface GameAction { + command: string + commandDescription: string + live: ( + player: GameObjectPlayer, + params: string[], + ) => Promise +} diff --git a/src/lib/game/actions/plantTreeAction.ts b/src/lib/game/actions/plantTreeAction.ts index 20dbf0a9..10271252 100644 --- a/src/lib/game/actions/plantTreeAction.ts +++ b/src/lib/game/actions/plantTreeAction.ts @@ -1,36 +1,36 @@ -import { Village } from '../chunks' import { PlantNewTreeScript } from '../scripts/plantNewTreeScript' -import { BaseAction } from './baseAction' -import { ANSWER } from '$lib/game/scenes/services/actionService' -import type { GameScene } from '$lib/game/types' +import { ANSWER } from '$lib/game/services/actionService' +import type { Game, GameObjectPlayer } from '$lib/game/types' +import { VillageChunk } from '$lib/game/services/chunk/villageChunk' +import type { GameAction } from '$lib/game/actions/interface' interface IPlantTreeActionOptions { - scene: GameScene + game: Game } -export class PlantTreeAction extends BaseAction { - scene: GameScene +export class PlantTreeAction implements GameAction { + command = 'plant' + commandDescription = '!plant' + game: Game - constructor({ scene }: IPlantTreeActionOptions) { - super({ command: 'plant', commandDescription: '!plant' }) - - this.scene = scene + constructor({ game }: IPlantTreeActionOptions) { + this.game = game } - async live(player) { + async live(player: GameObjectPlayer) { if (player.script && !player.script.isInterruptible) { return ANSWER.BUSY_ERROR } - if (this.scene.chunkNow instanceof Village) { - const target = this.scene.chunkNow.checkIfNeedToPlantTree() + if (this.game.chunkService.chunk instanceof VillageChunk) { + const target = this.game.chunkService.chunk.checkIfNeedToPlantTree() if (!target) { return ANSWER.NO_SPACE_AVAILABLE_ERROR } const plantNewTreeFunc = () => { - if (this.scene.chunkNow instanceof Village) { - this.scene.chunkNow.plantNewTree(target) + if (this.game.chunkService.chunk instanceof VillageChunk) { + this.game.chunkService.chunk.plantNewTree(target) } } diff --git a/src/lib/game/actions/voteAction.ts b/src/lib/game/actions/voteAction.ts index 331773d0..d6df2ab3 100644 --- a/src/lib/game/actions/voteAction.ts +++ b/src/lib/game/actions/voteAction.ts @@ -1,18 +1,19 @@ -import type { Poll } from '../common' -import { BaseAction } from './baseAction' -import { ANSWER } from '$lib/game/scenes/services/actionService' +import { ANSWER } from '$lib/game/services/actionService' +import type { Poll } from '$lib/game/common/poll' +import type { GameObjectPlayer } from '$lib/game/types' +import type { GameAction } from '$lib/game/actions/interface' interface IVoteActionOptions { poll: Poll } -export class VoteAction extends BaseAction { +export class VoteAction implements GameAction { + command: GameAction['command'] + commandDescription: GameAction['commandDescription'] #poll: Poll readonly #id: string constructor({ poll }: IVoteActionOptions) { - super({ command: 'go', commandDescription: '!go' }) - this.#id = poll.generatePollId() this.command = `go ${this.#id}` @@ -21,8 +22,8 @@ export class VoteAction extends BaseAction { this.#poll = poll } - async live(player) { - const isSuccess = this.poll.vote(player) + async live(player: GameObjectPlayer) { + const isSuccess = this.#poll.vote(player) if (!isSuccess) { return ANSWER.ALREADY_VOTED_ERROR } diff --git a/src/lib/game/baseGame.ts b/src/lib/game/baseGame.ts index efa350bb..e6dae826 100644 --- a/src/lib/game/baseGame.ts +++ b/src/lib/game/baseGame.ts @@ -1,41 +1,78 @@ import { Application, Container } from 'pixi.js' +import { createId } from '@paralleldrive/cuid2' import type { Game, GameObject, - GameScene, - GameSceneType, WebSocketMessage, + GameObjectPlayer, + GameSceneType, + GameStateResponse, + IGameInventoryItem, + IGameObjectRaider, WebSocketMessage, } from '$lib/game/types' -import type { Wagon } from '$lib/game/objects/wagon' import { AudioManager } from '$lib/game/utils/audioManager' import { BackgroundGenerator } from '$lib/game/utils/generators/background' import { AssetsManager } from '$lib/game/utils/assetsManager' +import { MovingScene } from '$lib/game/scenes/movingScene' +import { + MoveOffScreenAndSelfDestroyScript, +} from '$lib/game/scripts/moveOffScreenAndSelfDestroyScript' +import type { Wagon } from '$lib/game/services/interface' +import { getRandomInRange } from '$lib/random' +import { MoveToTargetScript } from '$lib/game/scripts/moveToTargetScript' +import { ChopTreeScript } from '$lib/game/scripts/chopTreeScript' +import { ActionService } from '$lib/game/services/actionService' +import { EventService } from '$lib/game/services/event/eventService' +import { TradeService } from '$lib/game/services/tradeService' +import { WagonService } from '$lib/game/services/wagonService' +import { RouteService } from '$lib/game/services/routeService' +import { ChunkService } from '$lib/game/services/chunk/chunkService' +import { TreeObject } from '$lib/game/objects/treeObject' +import { Group } from '$lib/game/common/group' +import { PlayerService } from '$lib/game/services/player/playerService' +import { Raider } from '$lib/game/objects/units/raider' export class BaseGame extends Container implements Game { - public children: GameObject[] = [] - public app!: Application - public audio!: AudioManager - public scene!: GameScene - public bg!: BackgroundGenerator - - public tick = 0 - public cameraOffsetX = 0 - public cameraMovementSpeedX = 0.008 - public cameraOffsetY = 0 - public cameraMovementSpeedY = 0.008 - public cameraX = 0 - public cameraY = 0 - public cameraPerfectX = 0 - public cameraPerfectY = 0 + id: string + children: Game['children'] = [] + app: Application + audio: Game['audio'] + scene!: Game['scene'] + bg!: BackgroundGenerator + tick: Game['tick'] = 0 + group: Group + + actionService: ActionService + eventService: EventService + tradeService: TradeService + wagonService: WagonService + routeService: RouteService + chunkService: ChunkService + playerService: PlayerService + + #cameraX = 0 + #cameraY = 0 + #cameraPerfectX = 0 + #cameraPerfectY = 0 constructor() { super() + this.id = createId() this.app = new Application() this.audio = new AudioManager() this.bg = new BackgroundGenerator(this.app) + this.group = new Group() + + this.actionService = new ActionService(this) + this.eventService = new EventService(this) + this.tradeService = new TradeService(this) + this.wagonService = new WagonService(this) + this.routeService = new RouteService(this) + this.chunkService = new ChunkService(this) + this.playerService = new PlayerService(this) } - public async init() { + async init() { await this.app.init({ background: '#239063', antialias: false, @@ -56,24 +93,27 @@ export class BaseGame extends Container implements Game { // WebSocketManager.init(this) - this.scene = new MovingScene({ game: this }) + this.initScene('MOVING') this.app.ticker.add(() => { this.tick = this.app.ticker.FPS - this.scene.live() - - this.animateObjects() - this.removeDestroyedObjects() - - const wagon = this.scene.wagonService.wagon - this.changeCameraPosition(wagon) - - this.moveCamera() + this.eventService.update() + this.tradeService.update() + this.wagonService.update() + this.routeService.update() + this.chunkService.update() + this.playerService.update() + this.#updateObjects() + this.#removeDestroyedObjects() + + const wagon = this.wagonService.wagon + this.#changeCameraPosition(wagon) + this.#moveCamera() }) } - public async play() { + async play() { this.audio.isEnabled = true // setInterval(() => { @@ -82,118 +122,70 @@ export class BaseGame extends Container implements Game { // }, 1000) } - public destroy() { + destroy() { this.audio.destroy() this.app.destroy() super.destroy() } - private changeCameraPosition(wagon: Wagon) { - const columnWidth = this.app.screen.width / 6 - const rowHeight = this.app.screen.height / 6 - - let leftPadding - = wagon.direction === 'LEFT' ? columnWidth * 4 : columnWidth * 2 - let topPadding = rowHeight * 3 - - if (wagon.speedPerSecond === 0) { - leftPadding = columnWidth * 3 - - if (wagon.state === 'IDLE' && !wagon.cargoType) { - // At Village stop - leftPadding = columnWidth - topPadding = rowHeight * 4 - } - } - - this.cameraPerfectX = -wagon.x + leftPadding - this.cameraPerfectY = -wagon.y + topPadding - - // If first load - if (Math.abs(-wagon.x - this.cameraX) > 3000) { - this.cameraX = this.cameraPerfectX - } - if (Math.abs(-wagon.y - this.cameraY) > 3000) { - this.cameraY = this.cameraPerfectY - } + removeObject(obj: GameObject) { + this.removeChild(obj) } - private moveCamera() { - const cameraSpeedPerSecond = 18 - const cameraDistance = cameraSpeedPerSecond / this.tick - - const bufferX = Math.abs(this.cameraPerfectX - this.cameraX) - const moduleX = this.cameraPerfectX - this.cameraX > 0 ? 1 : -1 - const addToX = bufferX > cameraDistance ? cameraDistance : bufferX - - if (this.cameraX !== this.cameraPerfectX) { - this.cameraX += addToX * moduleX - } - - const bufferY = Math.abs(this.cameraPerfectY - this.cameraY) - const moduleY = this.cameraPerfectY - this.cameraY > 0 ? 1 : -1 - const addToY = bufferY > cameraDistance ? cameraDistance : bufferY - - if (this.cameraY !== this.cameraPerfectY) { - this.cameraY += addToY * moduleY + initScene(scene: GameSceneType) { + if (this.scene) { + this.scene.destroy() } - // if (Math.abs(this.cameraOffsetX) >= 20) { - // this.cameraMovementSpeedX *= -1 - // } - // this.cameraOffsetX += this.cameraMovementSpeedX - // - // if (Math.abs(this.cameraOffsetY) >= 30) { - // this.cameraMovementSpeedY *= -1 - // } - // this.cameraOffsetY += this.cameraMovementSpeedY - - if (this.parent) { - this.parent.x = this.cameraX - this.parent.y = this.cameraY + if (scene === 'MOVING') { + this.scene = new MovingScene({ game: this }) } } - rebuildScene() { - this.removeChild(...this.children) + get activePlayers() { + return this.children.filter((obj) => obj.type === 'PLAYER') as GameObjectPlayer[] } - findObject(id: string) { - return this.children.find((obj) => obj.id === id) - } - - public initScene(scene: GameSceneType) { - if (this.scene) { - this.scene.destroy() + getWarehouseItems(): IGameInventoryItem[] | undefined { + const warehouse = this.chunkService.chunk?.warehouse + if (warehouse) { + return warehouse.inventory.items } - if (scene === 'MOVING') { - this.scene = new MovingScene({ game: this }) - } + return undefined } - public checkIfThisFlagIsTarget(id: string) { + checkIfThisFlagIsTarget(id: string): boolean { for (const obj of this.children) { if (obj.target?.id === id) { return true } } + return false } - animateObjects() { - for (const object of this.children) { - object?.animate() - object?.live() + initRaiders(count: number) { + for (let i = 0; i < count; i++) { + const flag = this.wagonService.randomOutFlag + + new Raider({ game: this, x: flag.x, y: flag.y }).init() } } - removeDestroyedObjects() { + stopRaid() { for (const object of this.children) { - if (object.state === 'DESTROYED') { - const index = this.children.indexOf(object) - this.children.splice(index, 1) - return + if (object instanceof Raider) { + const target = this.wagonService.randomOutFlag + const selfDestroyFunc = () => { + this.removeObject(object) + } + + object.script = new MoveOffScreenAndSelfDestroyScript({ + target, + object, + selfDestroyFunc, + }) } } } @@ -212,7 +204,7 @@ export class BaseGame extends Container implements Game { return } - this.findObject(object.id) + this.#findObject(object.id) } handleMessageEvent(event: WebSocketMessage['event']) { @@ -226,10 +218,173 @@ export class BaseGame extends Container implements Game { this.audio.playSound('MARCHING_WITH_HORNS') } if (event === 'SCENE_CHANGED') { - this.rebuildScene() + this.#rebuildScene() } if (event === 'IDEA_CREATED') { this.audio.playSound('YEAH') } } + + getState(): GameStateResponse { + return { + id: this.id, + commands: this.actionService.getAvailableCommands(), + events: this.eventService.events, + group: this.group.getGroup(), + wagon: this.wagonService.wagon, + chunk: this.chunkService.chunk, + route: this.routeService.getRoute(), + warehouseItems: this.getWarehouseItems(), + } + } + + #rebuildScene() { + this.removeChild(...this.children) + } + + #updateObjects() { + for (const object of this.children) { + object.animate() + object.live() + + if (object.type === 'PLAYER') { + this.#updatePlayer(object as GameObjectPlayer) + } + if (object.type === 'RAIDER') { + this.#updateRaider(object as IGameObjectRaider) + } + } + } + + #updatePlayer(object: GameObjectPlayer) { + if (object.script) { + return + } + + if (object.state === 'IDLE') { + const random = getRandomInRange(1, 150) + if (random <= 1) { + const target = this.wagonService.randomNearFlag + + object.script = new MoveToTargetScript({ + object, + target, + }) + } + } + } + + #updateRaider(object: IGameObjectRaider) { + if (object.script) { + return + } + + // If there is an available tree + const availableTree = this.chunkService.chunk?.availableTree + if (availableTree) { + const chopTreeFunc = (): boolean => { + object.chopTree() + if (!object.target || object.target.state === 'DESTROYED') { + object.state = 'IDLE' + if (object.target instanceof TreeObject) { + void object.inventory.addOrCreateItem( + 'WOOD', + object.target?.resource, + ) + } + return true + } + return false + } + + object.script = new ChopTreeScript({ + object, + target: availableTree, + chopTreeFunc, + }) + + return + } + + if (object.state === 'IDLE') { + const random = getRandomInRange(1, 100) + if (random <= 1) { + const randomObj = this.chunkService.chunk?.randomMovementFlag + if (!randomObj) { + return + } + object.setTarget(randomObj) + } + } + } + + #removeDestroyedObjects() { + for (const object of this.children) { + if (object.state === 'DESTROYED') { + const index = this.children.indexOf(object) + this.children.splice(index, 1) + return + } + } + } + + #changeCameraPosition(wagon: Wagon) { + const columnWidth = this.app.screen.width / 6 + const rowHeight = this.app.screen.height / 6 + + let leftPadding + = wagon.direction === 'LEFT' ? columnWidth * 4 : columnWidth * 2 + let topPadding = rowHeight * 3 + + if (wagon.speedPerSecond === 0) { + leftPadding = columnWidth * 3 + + if (wagon.state === 'IDLE' && !wagon.cargoType) { + // At Village stop + leftPadding = columnWidth + topPadding = rowHeight * 4 + } + } + + this.#cameraPerfectX = -wagon.x + leftPadding + this.#cameraPerfectY = -wagon.y + topPadding + + // If first load + if (Math.abs(-wagon.x - this.#cameraX) > 3000) { + this.#cameraX = this.#cameraPerfectX + } + if (Math.abs(-wagon.y - this.#cameraY) > 3000) { + this.#cameraY = this.#cameraPerfectY + } + } + + #moveCamera() { + const cameraSpeedPerSecond = 18 + const cameraDistance = cameraSpeedPerSecond / this.tick + + const bufferX = Math.abs(this.#cameraPerfectX - this.#cameraX) + const moduleX = this.#cameraPerfectX - this.#cameraX > 0 ? 1 : -1 + const addToX = bufferX > cameraDistance ? cameraDistance : bufferX + + if (this.#cameraX !== this.#cameraPerfectX) { + this.#cameraX += addToX * moduleX + } + + const bufferY = Math.abs(this.#cameraPerfectY - this.#cameraY) + const moduleY = this.#cameraPerfectY - this.#cameraY > 0 ? 1 : -1 + const addToY = bufferY > cameraDistance ? cameraDistance : bufferY + + if (this.#cameraY !== this.#cameraPerfectY) { + this.#cameraY += addToY * moduleY + } + + if (this.parent) { + this.parent.x = this.#cameraX + this.parent.y = this.#cameraY + } + } + + #findObject(id: string) { + return this.children.find((obj) => obj.id === id) + } } diff --git a/src/lib/game/chunks/forest.ts b/src/lib/game/chunks/forest.ts deleted file mode 100644 index 747df201..00000000 --- a/src/lib/game/chunks/forest.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Stone, Tree } from '../objects' -import { GameChunk } from './gameChunk' -import { getRandomInRange } from '$lib/random' -import type { GameScene, IGameChunkTheme, IGameForestChunk } from '$lib/game/types' - -interface IForestOptions { - center: IGameForestChunk['center'] - width: number - height: number - theme: IGameChunkTheme - scene: GameScene -} - -export class Forest extends GameChunk implements IGameForestChunk { - constructor({ width, height, center, theme, scene }: IForestOptions) { - super({ - title: 'Grand Wood', - type: 'FOREST', - width, - height, - center, - theme, - scene, - }) - - const treesToPrepare = Math.round( - (this.area.area.endX - this.area.area.startX) / 10, - ) - this.initTrees(treesToPrepare) - this.initStones(3) - } - - live() { - super.live() - - for (const obj of this.objects) { - void obj.live() - } - } - - initTrees(count: number) { - for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() - const size = getRandomInRange(75, 90) - const tree = new Tree({ - scene: this.scene, - x: point.x, - y: point.y, - size, - resource: 1, - health: 20, - variant: this.area.theme, - }) - this.objects.push(tree) - } - } - - initStones(count: number) { - for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() - const stone = new Stone({ - scene: this.scene, - x: point.x, - y: point.y, - resource: 1, - }) - this.objects.push(stone) - } - } -} diff --git a/src/lib/game/common/group.ts b/src/lib/game/common/group.ts index b01ca0ac..80af12cc 100644 --- a/src/lib/game/common/group.ts +++ b/src/lib/game/common/group.ts @@ -1,10 +1,9 @@ import { createId } from '@paralleldrive/cuid2' -import type { Player } from '../objects/units' -import type { IGameGroup, IGameObjectPlayer } from '$lib/game/types' +import type { GameObjectPlayer, IGameGroup } from '$lib/game/types' export class Group implements IGameGroup { id: string - players: IGameObjectPlayer[] = [] + players: GameObjectPlayer[] = [] constructor() { this.id = createId() @@ -13,17 +12,11 @@ export class Group implements IGameGroup { public getGroup(): IGameGroup { return { id: this.id, - players: this.players.map((p) => { - return { - ...p, - script: undefined, - live: undefined, - } - }), + players: this.players, } } - join(player: Player): boolean { + join(player: GameObjectPlayer): boolean { const check = this.findPlayer(player.id) if (check) { return false @@ -33,7 +26,7 @@ export class Group implements IGameGroup { return true } - remove(player: IGameObjectPlayer): boolean { + remove(player: GameObjectPlayer): boolean { const check = this.findPlayer(player.id) if (!check) { return false diff --git a/src/lib/game/common/inventory.ts b/src/lib/game/common/inventory.ts index 9caa134f..02b05ed2 100644 --- a/src/lib/game/common/inventory.ts +++ b/src/lib/game/common/inventory.ts @@ -40,7 +40,7 @@ export class Inventory implements IGameInventory { } } - public async reduceOrDestroyItem(type: ItemType, amount: number) { + async reduceOrDestroyItem(type: ItemType, amount: number): Promise { const item = this.checkIfAlreadyHaveItem(type) if (!item) { return false @@ -63,7 +63,7 @@ export class Inventory implements IGameInventory { return true } - public async addOrCreateItem(type: ItemType, amount: number) { + async addOrCreateItem(type: ItemType, amount: number): Promise { if (this.saveInDb) { return this.addOrCreateItemInDB(type, amount) } diff --git a/src/lib/game/common/poll.ts b/src/lib/game/common/poll.ts index 897cabf3..f4117b2c 100644 --- a/src/lib/game/common/poll.ts +++ b/src/lib/game/common/poll.ts @@ -1,10 +1,14 @@ import { createId } from '@paralleldrive/cuid2' import { VoteAction } from '../actions/voteAction' import { getRandomInRange } from '$lib/random' -import type { GameScene, IGameObjectPlayer, IGamePoll } from '$lib/game/types' +import type { + Game, + GameObjectPlayer, + IGamePoll, +} from '$lib/game/types' interface IPollOptions { - scene: GameScene + game: Game votesToSuccess: IGamePoll['votesToSuccess'] } @@ -15,10 +19,10 @@ export class Poll implements IGamePoll { public votesToSuccess: IGamePoll['votesToSuccess'] public votes: IGamePoll['votes'] = [] - public scene: GameScene + game: Game - constructor({ votesToSuccess, scene }: IPollOptions) { - this.scene = scene + constructor({ votesToSuccess, game }: IPollOptions) { + this.game = game this.id = createId() this.status = 'ACTIVE' @@ -27,7 +31,7 @@ export class Poll implements IGamePoll { this.action = new VoteAction({ poll: this }) } - public vote(player: IGameObjectPlayer): boolean { + public vote(player: GameObjectPlayer): boolean { if (this.votes.find((v) => v.id === player.id)) { return false } @@ -38,7 +42,7 @@ export class Poll implements IGamePoll { public generatePollId(): string { const id = getRandomInRange(1, 9).toString() - for (const event of this.scene.eventService.events) { + for (const event of this.game.eventService.events) { if (event.poll?.action.command === `go ${id}`) { return this.generatePollId() } diff --git a/src/lib/game/common/route.ts b/src/lib/game/common/route.ts index 1f024799..518dd801 100644 --- a/src/lib/game/common/route.ts +++ b/src/lib/game/common/route.ts @@ -1,5 +1,7 @@ -import { Flag } from '../objects' -import type { GameScene, IGameChunk, IGameRoute } from '$lib/game/types' +import { FlagObject } from '$lib/game/objects/flagObject' +import type { GameChunk } from '$lib/game/services/chunk/interface' +import type { Game } from '$lib/game/types' +import type { IGameRoute } from '$lib/game/services/interface' interface IRoutePoint { x: number @@ -14,30 +16,20 @@ interface IRouteArea { } interface IRouteOptions { - scene: GameScene + game: Game } export class Route implements IGameRoute { public startPoint!: IRoutePoint public endPoint!: IRoutePoint - public chunks: IGameChunk[] = [] + public chunks: GameChunk[] = [] - public scene: GameScene - public flags: Flag[] = [] + game: Game + public flags: FlagObject[] = [] public areas: IRouteArea[] = [] - constructor({ scene }: IRouteOptions) { - this.scene = scene - } - - public addChunk(chunk: IGameChunk) { - this.chunks.push({ - id: chunk.id, - type: chunk.type, - title: chunk.title, - center: chunk.center, - area: chunk.area, - }) + constructor({ game }: IRouteOptions) { + this.game = game } setEndPoint({ x, y }: IRoutePoint) { @@ -45,9 +37,9 @@ export class Route implements IGameRoute { } #addFlag({ x, y }: IRoutePoint) { - const movementFlag = new Flag({ - scene: this.scene, - type: 'WAGON_MOVEMENT', + const movementFlag = new FlagObject({ + game: this.game, + variant: 'WAGON_MOVEMENT', x, y, }) @@ -60,7 +52,7 @@ export class Route implements IGameRoute { this.flags.push(movementFlag) } - public addGlobalFlag(end: IRoutePoint) { + addGlobalFlag(end: IRoutePoint) { const prevGlobalFlag = this.flags[this.flags.length - 1] if (!prevGlobalFlag) { return this.#addFlag(end) @@ -70,18 +62,14 @@ export class Route implements IGameRoute { this.#addFlag({ x: end.x, y: end.y }) } - getNextFlag() { - return this.flags[0] - } - - removeFlag(flag: Flag) { - const index = this.flags.findIndex((f) => f.id === flag.id) + removeFlag(id: string) { + const index = this.flags.findIndex((f) => f.id === id) if (index >= 0) { this.flags.splice(index, 1) } } - #initArea(flag1: Flag, flag2: Flag) { + #initArea(flag1: FlagObject, flag2: FlagObject) { const offset = 150 const halfOffset = offset / 2 diff --git a/src/lib/game/components/wagonEngineCloudsContainer.ts b/src/lib/game/components/wagonEngineCloudsContainer.ts index d338b4be..0ee85fc7 100644 --- a/src/lib/game/components/wagonEngineCloudsContainer.ts +++ b/src/lib/game/components/wagonEngineCloudsContainer.ts @@ -1,7 +1,7 @@ import { type Container, Sprite } from 'pixi.js' import { GraphicsContainer } from './graphicsContainer' -import type { Wagon } from '$lib/game/objects' import { getRandomInRange } from '$lib/random' +import type { Wagon } from '$lib/game/services/interface' interface IWagonEngineCloudsContainerOptions { wagon: Wagon @@ -34,9 +34,9 @@ export class WagonEngineCloudsContainer extends GraphicsContainer { for (const container of this.children) { container.visible = true - container.x -= (speed / 3 + 2.5) / this.#wagon.scene.game.tick - container.y -= 3 / this.#wagon.scene.game.tick - container.alpha -= 0.5 / this.#wagon.scene.game.tick + container.x -= (speed / 3 + 2.5) / this.#wagon.game.tick + container.y -= 3 / this.#wagon.game.tick + container.alpha -= 0.5 / this.#wagon.game.tick if (container.alpha <= 0) { this.#remove(container) diff --git a/src/lib/game/objects/area.ts b/src/lib/game/objects/area.ts index 0a78b050..7db7c351 100644 --- a/src/lib/game/objects/area.ts +++ b/src/lib/game/objects/area.ts @@ -1,8 +1,8 @@ import { BaseObject } from './baseObject' -import type { GameScene, IGameObjectArea } from '$lib/game/types' +import type { Game, IGameObjectArea } from '$lib/game/types' interface IAreaOptions { - scene: GameScene + game: Game theme: IGameObjectArea['theme'] area: IGameObjectArea['area'] } @@ -11,11 +11,11 @@ export class Area extends BaseObject implements IGameObjectArea { public theme: IGameObjectArea['theme'] public area: IGameObjectArea['area'] - constructor({ scene, theme, area }: IAreaOptions) { + constructor({ game, theme, area }: IAreaOptions) { const x = area.startX const y = area.startY - super({ scene, x, y, type: 'AREA' }) + super({ game, x, y, type: 'AREA' }) this.theme = theme this.area = area @@ -30,9 +30,7 @@ export class Area extends BaseObject implements IGameObjectArea { } #initGraphics() { - this.scene.game.bg.changePaletteByTheme(this.theme) - - const bg = this.scene.game.bg.getGeneratedBackgroundTilingSprite() + const bg = this.game.bg.generateBackgroundTilingSprite(this.theme) bg.width = this.area.endX - this.area.startX bg.height = this.area.endY - this.area.startY diff --git a/src/lib/game/objects/baseObject.ts b/src/lib/game/objects/baseObject.ts index 88f5f04c..c36ba579 100644 --- a/src/lib/game/objects/baseObject.ts +++ b/src/lib/game/objects/baseObject.ts @@ -1,17 +1,18 @@ import { createId } from '@paralleldrive/cuid2' import { Container } from 'pixi.js' -import type { GameObject, GameScene, IGameScript } from '$lib/game/types' +import type { Game, GameObject } from '$lib/game/types' interface GameObjectOptions { - scene: GameScene + game: Game type: GameObject['type'] - id?: string + id?: GameObject['id'] x?: number y?: number } export class BaseObject extends Container implements GameObject { - id: GameObject['id'] + readonly #id: GameObject['id'] + #chunkId: GameObject['chunkId'] state: GameObject['state'] direction: GameObject['direction'] type: GameObject['type'] @@ -19,27 +20,33 @@ export class BaseObject extends Container implements GameObject { health!: GameObject['health'] speedPerSecond!: GameObject['speedPerSecond'] size!: GameObject['size'] + game: GameObject['game'] + script: GameObject['script'] + isOnWagonPath: GameObject['isOnWagonPath'] - scene: GameScene - script: IGameScript minDistance = 1 - isOnWagonPath = false - constructor({ scene, x, y, id, type }: GameObjectOptions) { + constructor({ game, x, y, id, type }: GameObjectOptions) { super() - this.scene = scene + this.game = game - this.id = id ?? createId() + this.#id = id ?? createId() this.x = x ?? 0 this.y = y ?? 0 this.type = type this.direction = 'RIGHT' this.state = 'IDLE' + this.script = undefined + this.isOnWagonPath = false + } - this.scene.game.addChild(this) + init() { + this.game.addChild(this) } + live() {} + animate(): void { this.zIndex = Math.round(this.y) } @@ -60,7 +67,7 @@ export class BaseObject extends Container implements GameObject { const distanceToY = this.#getDistanceToTargetY() // Fix diagonal speed - const speed = this.speedPerSecond / this.scene.game.tick + const speed = this.speedPerSecond / this.game.tick const finalSpeed = distanceToX > 0 && distanceToY > 0 ? speed * 0.75 : speed this.#moveX(finalSpeed > distanceToX ? distanceToX : finalSpeed) @@ -68,6 +75,18 @@ export class BaseObject extends Container implements GameObject { return true } + get id() { + return this.#id + } + + get chunkId(): string | undefined { + return this.#chunkId + } + + set chunkId(id: string | undefined) { + this.#chunkId = id + } + setTarget(target: GameObject) { this.target = target this.state = 'MOVING' diff --git a/src/lib/game/objects/wagon.ts b/src/lib/game/objects/baseWagon.ts similarity index 90% rename from src/lib/game/objects/wagon.ts rename to src/lib/game/objects/baseWagon.ts index 2fe88732..f4910dab 100644 --- a/src/lib/game/objects/wagon.ts +++ b/src/lib/game/objects/baseWagon.ts @@ -1,6 +1,5 @@ import { createId } from '@paralleldrive/cuid2' import { Sprite } from 'pixi.js' -import { Inventory } from '../common' import type { GraphicsContainer } from '../components/graphicsContainer' import { WagonCargoContainer } from '../components/wagonCargoContainer' import { WagonEngineCloudsContainer } from '../components/wagonEngineCloudsContainer' @@ -8,28 +7,30 @@ import { WagonEngineContainer } from '../components/wagonEngineContainer' import { WagonFuelBoxContainer } from '../components/wagonFuelBoxContainer' import { WagonWheelContainer } from '../components/wagonWheelContainer' import { BaseObject } from './baseObject' -import { Mechanic } from './units' -import type { GameScene, IGameObjectWagon } from '$lib/game/types' +import type { Game } from '$lib/game/types' +import { Inventory } from '$lib/game/common/inventory' +import { Mechanic } from '$lib/game/objects/units/mechanic' +import type { Wagon } from '$lib/game/services/interface' interface IWagonOptions { - scene: GameScene + game: Game x: number y: number } -export class Wagon extends BaseObject implements IGameObjectWagon { +export class BaseWagon extends BaseObject implements Wagon { public fuel!: number - public visibilityArea!: IGameObjectWagon['visibilityArea'] - public cargoType: IGameObjectWagon['cargoType'] + public visibilityArea!: Wagon['visibilityArea'] + public cargoType: Wagon['cargoType'] public children: GraphicsContainer[] = [] public cargo: Inventory | undefined public mechanic!: Mechanic - public serverDataArea!: IGameObjectWagon['visibilityArea'] - public collisionArea!: IGameObjectWagon['visibilityArea'] + public serverDataArea!: Wagon['visibilityArea'] + public collisionArea!: Wagon['visibilityArea'] - constructor({ scene, x, y }: IWagonOptions) { - super({ scene, x, y, type: 'WAGON' }) + constructor({ game, x, y }: IWagonOptions) { + super({ game, x, y, type: 'WAGON' }) this.speedPerSecond = 0 this.fuel = 2000 @@ -149,10 +150,11 @@ export class Wagon extends BaseObject implements IGameObjectWagon { #initMechanic() { this.mechanic = new Mechanic({ - scene: this.scene, + game: this.game, x: this.x, y: this.y, }) + this.mechanic.init() } #updateMechanic() { @@ -279,7 +281,7 @@ export class Wagon extends BaseObject implements IGameObjectWagon { #handleSoundByState() { if (this.state === 'MOVING') { - this.scene.game.audio.playSound('WAGON_MOVING') + this.game.audio.playSound('WAGON_MOVING') } } } diff --git a/src/lib/game/objects/buildings/baseBuilding.ts b/src/lib/game/objects/buildings/baseBuilding.ts index 227ad8d6..6a192392 100644 --- a/src/lib/game/objects/buildings/baseBuilding.ts +++ b/src/lib/game/objects/buildings/baseBuilding.ts @@ -1,25 +1,27 @@ import { createId } from '@paralleldrive/cuid2' -import { Inventory } from '../../common' import { BaseObject } from '../baseObject' import type { + Game, GameObjectBuildingType, - GameScene, IGameObjectBuilding, - ItemType, } from '$lib/game/types' +import { Inventory } from '$lib/game/common/inventory' interface IBuildingOptions { - scene: GameScene + game: Game x: number y: number type: GameObjectBuildingType + chunkId?: string } export class BaseBuilding extends BaseObject implements IGameObjectBuilding { - public inventory!: Inventory + inventory!: Inventory - constructor({ scene, x, y, type }: IBuildingOptions) { - super({ scene, x, y, type }) + constructor({ game, x, y, type, chunkId }: IBuildingOptions) { + super({ game, x, y, type }) + + this.chunkId = chunkId this.#initInventory() } @@ -41,12 +43,4 @@ export class BaseBuilding extends BaseObject implements IGameObjectBuilding { saveInDb: false, }) } - - getItemByType(type: ItemType) { - if (!this.inventory?.items) { - return - } - - return this.inventory.items.find((item) => item.type === type) - } } diff --git a/src/lib/game/objects/buildings/campfire.ts b/src/lib/game/objects/buildings/campfire.ts index a3f1a538..3e4cbe6f 100644 --- a/src/lib/game/objects/buildings/campfire.ts +++ b/src/lib/game/objects/buildings/campfire.ts @@ -1,21 +1,22 @@ import { type AnimatedSprite, Sprite } from 'pixi.js' import { FireParticlesContainer } from '../../components/fireParticlesContainer' -import { AssetsManager } from '../../utils' import { BaseBuilding } from './baseBuilding' -import type { GameScene, IGameBuildingCampfire } from '$lib/game/types' +import type { Game, IGameBuildingCampfire } from '$lib/game/types' +import { AssetsManager } from '$lib/game/utils/assetsManager' interface ICampfireOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Campfire extends BaseBuilding implements IGameBuildingCampfire { private fireAnimation!: AnimatedSprite private fireParticles!: FireParticlesContainer - constructor({ scene, x, y }: ICampfireOptions) { - super({ scene, x, y, type: 'CAMPFIRE' }) + constructor({ game, x, y, chunkId }: ICampfireOptions) { + super({ game, x, y, chunkId, type: 'CAMPFIRE' }) this.initGraphics() } @@ -51,7 +52,7 @@ export class Campfire extends BaseBuilding implements IGameBuildingCampfire { } if (this.state === 'IDLE') { - this.scene.game.audio.playSound('FIRE_BURN') + this.game.audio.playSound('FIRE_BURN') } } } diff --git a/src/lib/game/objects/buildings/constructionArea.ts b/src/lib/game/objects/buildings/constructionArea.ts index 44fc49de..79149d2c 100644 --- a/src/lib/game/objects/buildings/constructionArea.ts +++ b/src/lib/game/objects/buildings/constructionArea.ts @@ -1,18 +1,22 @@ import { Sprite } from 'pixi.js' import { BaseBuilding } from './baseBuilding' -import type { GameScene, IGameBuildingConstructionArea } from '$lib/game/types' +import type { + Game, + IGameBuildingConstructionArea, +} from '$lib/game/types' interface IConstructionAreaOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class ConstructionArea extends BaseBuilding implements IGameBuildingConstructionArea { - constructor({ scene, x, y }: IConstructionAreaOptions) { - super({ scene, x, y, type: 'CONSTRUCTION_AREA' }) + constructor({ game, x, y, chunkId }: IConstructionAreaOptions) { + super({ game, x, y, chunkId, type: 'CONSTRUCTION_AREA' }) this.#initGraphics() } diff --git a/src/lib/game/objects/buildings/store.ts b/src/lib/game/objects/buildings/store.ts index 87b47807..5a553fb2 100644 --- a/src/lib/game/objects/buildings/store.ts +++ b/src/lib/game/objects/buildings/store.ts @@ -1,16 +1,17 @@ import { Sprite } from 'pixi.js' import { BaseBuilding } from './baseBuilding' -import type { GameScene, IGameBuildingStore } from '$lib/game/types' +import type { Game, IGameBuildingStore } from '$lib/game/types' interface IStoreOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Store extends BaseBuilding implements IGameBuildingStore { - constructor({ scene, x, y }: IStoreOptions) { - super({ scene, x, y, type: 'STORE' }) + constructor({ game, x, y, chunkId }: IStoreOptions) { + super({ game, x, y, chunkId, type: 'STORE' }) this.#initGraphics() } diff --git a/src/lib/game/objects/buildings/wagonStop.ts b/src/lib/game/objects/buildings/wagonStop.ts index a6e7fa92..e1330816 100644 --- a/src/lib/game/objects/buildings/wagonStop.ts +++ b/src/lib/game/objects/buildings/wagonStop.ts @@ -1,16 +1,17 @@ import { Sprite } from 'pixi.js' import { BaseBuilding } from './baseBuilding' -import type { GameScene, IGameBuildingWagonStop } from '$lib/game/types' +import type { Game, IGameBuildingWagonStop } from '$lib/game/types' interface IWagonStopOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class WagonStop extends BaseBuilding implements IGameBuildingWagonStop { - constructor({ scene, x, y }: IWagonStopOptions) { - super({ scene, x, y, type: 'WAGON_STOP' }) + constructor({ game, x, y, chunkId }: IWagonStopOptions) { + super({ game, x, y, chunkId, type: 'WAGON_STOP' }) this.#initGraphics() } diff --git a/src/lib/game/objects/buildings/warehouse.ts b/src/lib/game/objects/buildings/warehouse.ts index cfcaaaef..3faf0037 100644 --- a/src/lib/game/objects/buildings/warehouse.ts +++ b/src/lib/game/objects/buildings/warehouse.ts @@ -1,19 +1,20 @@ import { Sprite } from 'pixi.js' import { BuildingInterface } from '../../components/buildingInterface' import { BaseBuilding } from './baseBuilding' -import type { GameScene, IGameBuildingWarehouse } from '$lib/game/types' +import type { Game, IGameBuildingWarehouse } from '$lib/game/types' interface IWarehouseOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Warehouse extends BaseBuilding implements IGameBuildingWarehouse { public interface!: BuildingInterface - constructor({ scene, x, y }: IWarehouseOptions) { - super({ scene, x, y, type: 'WAREHOUSE' }) + constructor({ game, x, y, chunkId }: IWarehouseOptions) { + super({ game, x, y, chunkId, type: 'WAREHOUSE' }) this.#initGraphics() // this.#initInterface() diff --git a/src/lib/game/objects/creatures/rabbit.ts b/src/lib/game/objects/creatures/rabbit.ts index a11706b5..1082ff4a 100644 --- a/src/lib/game/objects/creatures/rabbit.ts +++ b/src/lib/game/objects/creatures/rabbit.ts @@ -1,18 +1,21 @@ import { Sprite } from 'pixi.js' import { BaseObject } from '../baseObject' -import type { GameScene, IGameObjectRabbit } from '$lib/game/types' +import type { Game, IGameObjectRabbit } from '$lib/game/types' interface RabbitOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Rabbit extends BaseObject implements IGameObjectRabbit { public animationAngle = 0 - constructor({ scene, x, y }: RabbitOptions) { - super({ scene, x, y, type: 'RABBIT' }) + constructor({ game, x, y, chunkId }: RabbitOptions) { + super({ game, x, y, type: 'RABBIT' }) + + this.chunkId = chunkId this.#init() } diff --git a/src/lib/game/objects/creatures/wolf.ts b/src/lib/game/objects/creatures/wolf.ts index ef540753..3df186a4 100644 --- a/src/lib/game/objects/creatures/wolf.ts +++ b/src/lib/game/objects/creatures/wolf.ts @@ -1,18 +1,21 @@ import { Sprite } from 'pixi.js' import { BaseObject } from '../baseObject' -import type { GameScene, IGameObjectWolf } from '$lib/game/types' +import type { Game, IGameObjectWolf } from '$lib/game/types' interface IWolfOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Wolf extends BaseObject implements IGameObjectWolf { #animationSlowSpeed = 0.1 - constructor({ scene, x, y }: IWolfOptions) { - super({ scene, x, y, type: 'WOLF' }) + constructor({ game, x, y, chunkId }: IWolfOptions) { + super({ game, x, y, type: 'WOLF' }) + + this.chunkId = chunkId this.init() } diff --git a/src/lib/game/objects/flagObject.ts b/src/lib/game/objects/flagObject.ts index 00d33e69..87b46d5d 100644 --- a/src/lib/game/objects/flagObject.ts +++ b/src/lib/game/objects/flagObject.ts @@ -1,12 +1,13 @@ import { Sprite } from 'pixi.js' import { BaseObject } from './baseObject' -import type { GameObjectFlag, GameScene } from '$lib/game/types' +import type { Game, GameObjectFlag } from '$lib/game/types' interface FlagOptions { - scene: GameScene + game: Game x: number y: number variant: GameObjectFlag['variant'] + chunkId?: string offsetX?: number offsetY?: number } @@ -18,9 +19,10 @@ export class FlagObject extends BaseObject implements GameObjectFlag { public offsetX: number public offsetY: number - constructor({ scene, x, y, variant, offsetX, offsetY }: FlagOptions) { - super({ scene, x, y, type: 'FLAG' }) + constructor({ game, chunkId, x, y, variant, offsetX, offsetY }: FlagOptions) { + super({ game, x, y, type: 'FLAG' }) + this.chunkId = chunkId this.variant = variant this.isReserved = false this.offsetX = offsetX ?? 0 @@ -35,17 +37,19 @@ export class FlagObject extends BaseObject implements GameObjectFlag { super.live() if (this.target?.state === 'DESTROYED') { - this.removeTarget() + this.target = undefined } } animate() { - if (this.scene.game.checkIfThisFlagIsTarget(this.id)) { + super.animate() + + if (this.game.checkIfThisFlagIsTarget(this.id)) { this.visible = true return } - if (this.type === 'WAGON_MOVEMENT') { + if (this.variant === 'WAGON_MOVEMENT') { this.visible = true return } diff --git a/src/lib/game/objects/lake.ts b/src/lib/game/objects/lake.ts index 4ffaad0a..95404fc6 100644 --- a/src/lib/game/objects/lake.ts +++ b/src/lib/game/objects/lake.ts @@ -1,19 +1,22 @@ -import { AssetsManager } from '../utils' import { BaseObject } from './baseObject' import { Water } from './water' -import type { GameScene, IGameObjectLake } from '$lib/game/types' +import type { Game, IGameObjectLake } from '$lib/game/types' +import { AssetsManager } from '$lib/game/utils/assetsManager' interface ILakeOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Lake extends BaseObject implements IGameObjectLake { public water: Water[] = [] - constructor({ scene, x, y }: ILakeOptions) { - super({ scene, x, y, type: 'LAKE' }) + constructor({ game, x, y, chunkId }: ILakeOptions) { + super({ game, x, y, type: 'LAKE' }) + + this.chunkId = chunkId this.#generate(13) this.#initGraphics() @@ -38,11 +41,11 @@ export class Lake extends BaseObject implements IGameObjectLake { } #draw(x: number, y: number) { - const water = new Water({ scene: this.scene, x: x * 32, y: y * 32 }) + const water = new Water({ game: this.game, x: x * 32, y: y * 32 }) this.water.push(water) } - init(width: number, height: number) { + initWater(width: number, height: number) { const gridX = Math.ceil(width / 32) const gridY = Math.floor(height / 32) @@ -59,7 +62,7 @@ export class Lake extends BaseObject implements IGameObjectLake { // continue // } - const water = new Water({ scene: this.scene, x, y }) + const water = new Water({ game: this.game, x, y }) this.water.push(water) } } diff --git a/src/lib/game/objects/stoneObject.ts b/src/lib/game/objects/stoneObject.ts index 3fe56479..7332c863 100644 --- a/src/lib/game/objects/stoneObject.ts +++ b/src/lib/game/objects/stoneObject.ts @@ -1,12 +1,13 @@ import { Sprite } from 'pixi.js' import { BaseObject } from './baseObject' import { getRandomInRange } from '$lib/random' -import type { GameObjectStone, GameScene } from '$lib/game/types' +import type { Game, GameObjectStone } from '$lib/game/types' interface StoneOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string resource?: number size?: number health?: number @@ -20,9 +21,10 @@ export class StoneObject extends BaseObject implements GameObjectStone { public animationAngle = 0 public animationHighSpeed = 0.05 - constructor({ scene, x, y, resource, size }: StoneOptions) { - super({ scene, x, y, type: 'STONE' }) + constructor({ game, x, y, resource, size, chunkId }: StoneOptions) { + super({ game, x, y, type: 'STONE' }) + this.chunkId = chunkId this.resource = resource ?? getRandomInRange(1, 5) this.size = size ?? 100 diff --git a/src/lib/game/objects/treeObject.ts b/src/lib/game/objects/treeObject.ts index d645476e..ac9b349c 100644 --- a/src/lib/game/objects/treeObject.ts +++ b/src/lib/game/objects/treeObject.ts @@ -1,12 +1,13 @@ import { Sprite } from 'pixi.js' import { BaseObject } from './baseObject' import { getRandomInRange } from '$lib/random' -import type { GameObjectTree, GameScene } from '$lib/game/types' +import type { Game, GameObjectTree } from '$lib/game/types' interface TreeOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string resource?: number size?: number health?: number @@ -28,7 +29,7 @@ export class TreeObject extends BaseObject implements GameObjectTree { private animationSpeedPerSecond = 3 constructor({ - scene, + game, x, y, resource, @@ -36,9 +37,11 @@ export class TreeObject extends BaseObject implements GameObjectTree { health, theme, variant, + chunkId, }: TreeOptions) { - super({ scene, x, y, type: 'TREE' }) + super({ game, x, y, type: 'TREE' }) + this.chunkId = chunkId this.resource = resource ?? getRandomInRange(1, 5) this.size = size ?? 100 this.health = health ?? 100 @@ -105,45 +108,45 @@ export class TreeObject extends BaseObject implements GameObjectTree { } #getSprite() { - if (this.variant === 'GREEN') { - return Sprite.from(`tree${this.type}Green`) + if (this.theme === 'GREEN') { + return Sprite.from(`tree${this.variant}Green`) } - if (this.variant === 'BLUE') { - return Sprite.from(`tree${this.type}Blue`) + if (this.theme === 'BLUE') { + return Sprite.from(`tree${this.variant}Blue`) } - if (this.variant === 'STONE') { - return Sprite.from(`tree${this.type}Stone`) + if (this.theme === 'STONE') { + return Sprite.from(`tree${this.variant}Stone`) } - if (this.variant === 'TEAL') { - return Sprite.from(`tree${this.type}Teal`) + if (this.theme === 'TEAL') { + return Sprite.from(`tree${this.variant}Teal`) } - if (this.variant === 'TOXIC') { - return Sprite.from(`tree${this.type}Toxic`) + if (this.theme === 'TOXIC') { + return Sprite.from(`tree${this.variant}Toxic`) } - if (this.variant === 'VIOLET') { - return Sprite.from(`tree${this.type}Violet`) + if (this.theme === 'VIOLET') { + return Sprite.from(`tree${this.variant}Violet`) } } #shakeAnimation() { if (Math.abs(this.angle) < 3) { - this.angle += (this.animationSpeedPerSecond * 5) / this.scene.game.tick + this.angle += (this.animationSpeedPerSecond * 5) / this.game.tick return } this.animationSpeedPerSecond *= -1 this.angle - += ((this.animationSpeedPerSecond * 5) / this.scene.game.tick) * 10 + += ((this.animationSpeedPerSecond * 5) / this.game.tick) * 10 } #shakeOnWind() { if (Math.abs(this.angle) < 1.8) { - this.angle += this.animationSpeedPerSecond / this.scene.game.tick + this.angle += this.animationSpeedPerSecond / this.game.tick return } this.animationSpeedPerSecond *= -1 - this.angle += (this.animationSpeedPerSecond / this.scene.game.tick) * 10 + this.angle += (this.animationSpeedPerSecond / this.game.tick) * 10 } #checkHealth() { @@ -169,7 +172,7 @@ export class TreeObject extends BaseObject implements GameObjectTree { return } - this.size += this.growSpeedPerSecond / this.scene.game.tick + this.size += this.growSpeedPerSecond / this.game.tick } #getRandomVariant(): GameObjectTree['variant'] { diff --git a/src/lib/game/objects/units/courier.ts b/src/lib/game/objects/units/courier.ts index 8588d9dd..4c469ea9 100644 --- a/src/lib/game/objects/units/courier.ts +++ b/src/lib/game/objects/units/courier.ts @@ -1,20 +1,22 @@ import { generateUnitUserName } from '../../common/generators/unitName' import { generateUnitTop } from '../../common/generators/unitTop' import { UnitObject } from './unitObject' -import type { GameScene, IGameObjectCourier } from '$lib/game/types' +import type { Game, IGameObjectCourier } from '$lib/game/types' interface CourierOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Courier extends UnitObject implements IGameObjectCourier { - constructor({ scene, x, y }: CourierOptions) { + constructor({ game, x, y, chunkId }: CourierOptions) { super({ - scene, + game, x, y, + chunkId, type: 'VILLAGE_UNIT', }) diff --git a/src/lib/game/objects/units/farmer.ts b/src/lib/game/objects/units/farmer.ts index 52d43872..66fe8a8b 100644 --- a/src/lib/game/objects/units/farmer.ts +++ b/src/lib/game/objects/units/farmer.ts @@ -1,20 +1,22 @@ import { generateUnitUserName } from '../../common/generators/unitName' import { generateUnitTop } from '../../common/generators/unitTop' import { UnitObject } from './unitObject' -import type { GameScene, IGameObjectFarmer } from '$lib/game/types' +import type { Game, IGameObjectFarmer } from '$lib/game/types' interface IFarmerOptions { - scene: GameScene + game: Game x: number y: number + chunkId?: string } export class Farmer extends UnitObject implements IGameObjectFarmer { - constructor({ scene, x, y }: IFarmerOptions) { + constructor({ game, x, y, chunkId }: IFarmerOptions) { super({ - scene, + game, x, y, + chunkId, type: 'VILLAGE_UNIT', }) diff --git a/src/lib/game/objects/units/mechanic.ts b/src/lib/game/objects/units/mechanic.ts index 6f6a38da..8b8acc97 100644 --- a/src/lib/game/objects/units/mechanic.ts +++ b/src/lib/game/objects/units/mechanic.ts @@ -1,16 +1,16 @@ import { UnitObject } from './unitObject' -import type { GameScene, IGameObjectMechanic } from '$lib/game/types' +import type { Game, IGameObjectMechanic } from '$lib/game/types' interface IMechanicOptions { - scene: GameScene + game: Game x: number y: number } export class Mechanic extends UnitObject implements IGameObjectMechanic { - constructor({ scene, x, y }: IMechanicOptions) { + constructor({ game, x, y }: IMechanicOptions) { super({ - scene, + game, x, y, type: 'MECHANIC', diff --git a/src/lib/game/objects/units/player.ts b/src/lib/game/objects/units/player.ts index 2de07774..04dc937f 100644 --- a/src/lib/game/objects/units/player.ts +++ b/src/lib/game/objects/units/player.ts @@ -1,27 +1,32 @@ -import { Inventory, Skill } from '../../common' import { UnitObject } from './unitObject' import { getRandomInRange } from '$lib/random' -import type { GameScene, IGameObjectPlayer, IGameSkill } from '$lib/game/types' +import type { + Game, + GameObjectPlayer, + IGameSkill, +} from '$lib/game/types' +import { Skill } from '$lib/game/common/skill' +import { Inventory } from '$lib/game/common/inventory' interface PlayerOptions { - scene: GameScene + game: Game id?: string x: number y: number } -export class Player extends UnitObject implements IGameObjectPlayer { +export class Player extends UnitObject implements GameObjectPlayer { reputation!: number villainPoints!: number refuellerPoints!: number raiderPoints!: number skills!: Skill[] - lastActionAt!: IGameObjectPlayer['lastActionAt'] + lastActionAt!: GameObjectPlayer['lastActionAt'] public inventoryId?: string - constructor({ scene, id, x, y }: PlayerOptions) { - super({ scene, id, x, y, type: 'PLAYER' }) + constructor({ game, id, x, y }: PlayerOptions) { + super({ game, id, x, y, type: 'PLAYER' }) this.speedPerSecond = 2 void this.initFromDB() diff --git a/src/lib/game/objects/units/raider.ts b/src/lib/game/objects/units/raider.ts index 89d66397..da185cc7 100644 --- a/src/lib/game/objects/units/raider.ts +++ b/src/lib/game/objects/units/raider.ts @@ -1,16 +1,16 @@ import { UnitObject } from './unitObject' -import type { GameScene, IGameObjectRaider } from '$lib/game/types' +import type { Game, IGameObjectRaider } from '$lib/game/types' interface RaiderOptions { - scene: GameScene + game: Game x: number y: number } export class Raider extends UnitObject implements IGameObjectRaider { - constructor({ scene, x, y }: RaiderOptions) { + constructor({ game, x, y }: RaiderOptions) { super({ - scene, + game, x, y, type: 'RAIDER', diff --git a/src/lib/game/objects/units/trader.ts b/src/lib/game/objects/units/trader.ts index 8a15ee8a..48c4a786 100644 --- a/src/lib/game/objects/units/trader.ts +++ b/src/lib/game/objects/units/trader.ts @@ -1,18 +1,18 @@ import { generateUnitUserName } from '../../common/generators/unitName' import { generateUnitTop } from '../../common/generators/unitTop' import { UnitObject } from './unitObject' -import type { GameScene, IGameObjectTrader } from '$lib/game/types' +import type { Game, IGameObjectTrader } from '$lib/game/types' interface ITraderOptions { - scene: GameScene + game: Game x: number y: number } export class Trader extends UnitObject implements IGameObjectTrader { - constructor({ scene, x, y }: ITraderOptions) { + constructor({ game, x, y }: ITraderOptions) { super({ - scene, + game, x, y, type: 'TRADER', diff --git a/src/lib/game/objects/units/unitObject.ts b/src/lib/game/objects/units/unitObject.ts index a9ec8c71..af902f0e 100644 --- a/src/lib/game/objects/units/unitObject.ts +++ b/src/lib/game/objects/units/unitObject.ts @@ -1,26 +1,31 @@ import { createId } from '@paralleldrive/cuid2' import type { AnimatedSprite } from 'pixi.js' -import { Inventory } from '../../common' import { DialogueInterface } from '../../components/dialogueInterface' import type { GraphicsContainer } from '../../components/graphicsContainer' import { UnitHairContainer } from '../../components/unitHairContainer' import { UnitHeadContainer } from '../../components/unitHeadContainer' import { UnitInterface } from '../../components/unitInterface' import { UnitTopContainer } from '../../components/unitTopContainer' -import { AssetsManager } from '../../utils' import { FlagObject } from '../flagObject' import { BaseObject } from '../baseObject' import { StoneObject } from '../stoneObject' import { TreeObject } from '../treeObject' import { getRandomInRange } from '$lib/random' -import type { GameObject, GameScene, IGameObjectUnit } from '$lib/game/types' - -interface IUnitOptions { - scene: GameScene +import type { + Game, + GameObject, + IGameObjectUnit, +} from '$lib/game/types' +import { Inventory } from '$lib/game/common/inventory' +import { AssetsManager } from '$lib/game/utils/assetsManager' + +interface UnitObjectOptions { + game: Game id?: string x: number y: number type: GameObject['type'] + chunkId?: string } export class UnitObject extends BaseObject implements IGameObjectUnit { @@ -36,30 +41,31 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { private readonly animationMovingLeft!: AnimatedSprite private readonly animationMovingRight!: AnimatedSprite - constructor({ scene, x, y, id, type }: IUnitOptions) { - super({ scene, x, y, id, type }) + constructor({ game, x, y, id, type, chunkId }: UnitObjectOptions) { + super({ game, x, y, id, type }) - this.initInventory() this.initVisual() - this.initDialogue() + this.chunkId = chunkId this.coins = 0 this.state = 'IDLE' this.animationMovingRight = AssetsManager.getAnimatedSpriteHero('RIGHT') this.animationMovingLeft = AssetsManager.getAnimatedSpriteHero('LEFT') - this.initGraphics() + this.#initInventory() + this.#initDialogue() + this.#initGraphics() } live() { - this.handleMessages() + this.#handleMessages() if (this.script) { return this.script.live() } } - private initInventory() { + #initInventory() { this.inventory = new Inventory({ objectId: this.id, id: createId(), @@ -75,7 +81,7 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { } } - private initDialogue() { + #initDialogue() { this.dialogue = { messages: [], } @@ -93,14 +99,14 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { }) } - public handleMessages() { + #handleMessages() { const random = getRandomInRange(1, 200) if (random === 1) { this.dialogue.messages.splice(0, 1) } } - public chopTree() { + chopTree() { if (this.target instanceof TreeObject && this.target.state !== 'DESTROYED') { this.direction = 'RIGHT' this.state = 'CHOPPING' @@ -110,7 +116,7 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { } } - public mineStone() { + mineStone() { if (this.target instanceof StoneObject && this.target.state !== 'DESTROYED') { this.direction = 'RIGHT' this.state = 'MINING' @@ -131,7 +137,7 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { } } - private initGraphics() { + #initGraphics() { const top = this.initTop() const head = this.initHead() const hair = this.initHair() @@ -166,7 +172,7 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { this.dialogueInterface = new DialogueInterface(this) } - public animate() { + animate() { super.animate() this.zIndex = Math.round(this.y + 1) @@ -273,21 +279,21 @@ export class UnitObject extends BaseObject implements IGameObjectUnit { handleSoundByState() { if (this.state === 'CHOPPING') { if (this.inventory?.items.find((item) => item.type === 'AXE')) { - this.scene.game.audio.playSound('CHOP_HIT') + this.game.audio.playSound('CHOP_HIT') return } - this.scene.game.audio.playSound('HAND_HIT') + this.game.audio.playSound('HAND_HIT') return } if (this.state === 'MINING') { if (this.inventory?.items.find((item) => item.type === 'PICKAXE')) { - this.scene.game.audio.playSound('MINE_HIT') + this.game.audio.playSound('MINE_HIT') return } - this.scene.game.audio.playSound('HAND_HIT') + this.game.audio.playSound('HAND_HIT') } } } diff --git a/src/lib/game/objects/water.ts b/src/lib/game/objects/water.ts index d3cc8643..bce53b1b 100644 --- a/src/lib/game/objects/water.ts +++ b/src/lib/game/objects/water.ts @@ -1,14 +1,14 @@ import { BaseObject } from './baseObject' -import type { GameScene, IGameObjectWater } from '$lib/game/types' +import type { Game, IGameObjectWater } from '$lib/game/types' interface IWaterOptions { - scene: GameScene + game: Game x: number y: number } export class Water extends BaseObject implements IGameObjectWater { - constructor({ scene, x, y }: IWaterOptions) { - super({ scene, x, y, type: 'WATER' }) + constructor({ game, x, y }: IWaterOptions) { + super({ game, x, y, type: 'WATER' }) } } diff --git a/src/lib/game/scenes/baseScene.ts b/src/lib/game/scenes/baseScene.ts deleted file mode 100644 index c2a95f81..00000000 --- a/src/lib/game/scenes/baseScene.ts +++ /dev/null @@ -1,468 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { type GameChunk, Village } from '../chunks' -import { Group, Route } from '../common' -import { ChopTreeScript } from '../scripts/chopTreeScript' -import { MoveOffScreenAndSelfDestroyScript } from '../scripts/moveOffScreenAndSelfDestroyScript' -import { MoveToTargetScript } from '../scripts/moveToTargetScript' -import { WagonService } from './services/wagonService' -import { TradeService } from './services/tradeService' -import { ActionService } from './services/actionService' -import { EventService } from './services/eventService' -import { getRandomInRange } from '$lib/random' -import type { - Game, - GameObject, - GameScene, - GetSceneResponse, - IGameChunk, IGameChunkTheme, IGameInventoryItem, -} from '$lib/game/types' -import { getDateMinusMinutes } from '$lib/date' -import { RouteService } from '$lib/game/scenes/services/routeService' - -interface IGameSceneOptions { - game: Game -} - -export class BaseScene implements GameScene { - game: Game - objects: GameObject[] = [] - group: Group - chunks: GameChunk[] = [] - chunkNow: GameChunk | undefined - - actionService: ActionService - eventService: EventService - tradeService: TradeService - wagonService: WagonService - routeService: RouteService - - readonly #id: string - - constructor({ game }: IGameSceneOptions) { - this.game = game - - this.#id = createId() - - this.group = new Group() - this.actionService = new ActionService({ scene: this }) - this.eventService = new EventService({ scene: this }) - this.tradeService = new TradeService({ scene: this }) - this.wagonService = new WagonService({ scene: this }) - this.routeService = new RouteService({ scene: this }) - } - - live() { - this.eventService.update() - this.tradeService.update() - this.wagonService.update() - this.routeService.update() - this.updateObjects() - this.updateChunks() - this.updateChunkNow() - } - - destroy() { - this.objects = [] - } - - get id() { - return this.#id - } - - getChunkNow(): IGameChunk | null { - if (!this.chunkNow) { - return null - } - - return { - id: this.chunkNow.id, - title: this.chunkNow.title, - type: this.chunkNow.type, - center: this.chunkNow.center, - area: this.chunkNow.area, - } - } - - getWarehouseItems(): IGameInventoryItem[] | undefined { - if (this.chunkNow instanceof Village) { - const warehouse = this.chunkNow.getWarehouse() - if (warehouse) { - return warehouse.inventory.items - } - } - - return undefined - } - - getInfo(): GetSceneResponse { - return { - id: this.id, - commands: this.actionService.getAvailableCommands(), - events: this.eventService.getEvents(), - group: this.group.getGroup(), - wagon: this.wagonService.wagon, - chunk: this.getChunkNow(), - route: this.routeService.getRoute(), - warehouseItems: this.getWarehouseItems(), - } - } - - updateObjects() { - this.removeInactivePlayers() - - for (const obj of this.objects) { - this.removeDestroyedObject(obj) - - if (obj instanceof Trader) { - this.tradeService.updateTrader(obj) - continue - } - if (obj instanceof Player) { - this.updatePlayer(obj) - continue - } - if (obj instanceof Raider) { - this.updateRaider(obj) - continue - } - - void obj.live() - } - } - - updateChunks() { - for (const chunk of this.chunks) { - for (const object of chunk.objects) { - if (object.state === 'DESTROYED') { - chunk.removeObject(object) - } - } - - chunk.live() - } - } - - updateChunkNow() { - this.chunkNow = undefined - - for (const chunk of this.chunks) { - const isWagonOnThisChunk = chunk.checkIfPointIsInArea({ - x: this.wagonService.wagon.x, - y: this.wagonService.wagon.y, - }) - if (isWagonOnThisChunk) { - this.chunkNow = chunk - } - } - } - - updatePlayer(object: Player) { - object.live() - - if (object.script) { - return - } - - if (object.state === 'IDLE') { - const random = getRandomInRange(1, 150) - if (random <= 1) { - const target = this.wagonService.findRandomNearFlag() - - object.script = new MoveToTargetScript({ - object, - target, - }) - } - } - } - - updateRabbit(object: Rabbit) { - object.live() - - if (object.state === 'IDLE') { - const random = getRandomInRange(1, 100) - if (random <= 1) { - const randomObj = this.findRandomMovementFlag() - if (!randomObj) { - return - } - object.setTarget(randomObj) - } - } - } - - updateWolf(object: Wolf) { - object.live() - - if (object.state === 'IDLE') { - const random = getRandomInRange(1, 100) - if (random <= 1) { - const randomObj = this.findRandomMovementFlag() - if (!randomObj) { - return - } - object.setTarget(randomObj) - } - } - } - - updateRaider(object: Raider) { - object.live() - - if (object.script) { - return - } - - // If there is an available tree - const availableTree = this.chunkNow?.getAvailableTree() - if (availableTree) { - const chopTreeFunc = (): boolean => { - object.chopTree() - if (!object.target || object.target.state === 'DESTROYED') { - object.state = 'IDLE' - if (object.target instanceof Tree) { - void object.inventory.addOrCreateItem( - 'WOOD', - object.target?.resource, - ) - } - return true - } - return false - } - - object.script = new ChopTreeScript({ - object, - target: availableTree, - chopTreeFunc, - }) - - return - } - - if (object.state === 'IDLE') { - const random = getRandomInRange(1, 100) - if (random <= 1) { - const randomObj = this.findRandomMovementFlag() - if (!randomObj) { - return - } - object.setTarget(randomObj) - } - } - } - - removeObject(object: GameObject) { - const index = this.objects.indexOf(object) - this.objects.splice(index, 1) - } - - async findOrCreatePlayer(id: string) { - const player = this.findPlayer(id) - if (!player && this.actionService.isActionPossible('CREATE_NEW_PLAYER')) { - return this.createPlayer(id) - } - return player - } - - public findPlayer(id: string) { - const player = this.objects.find((p) => p.id === id) - if (player instanceof Player) { - return player - } - } - - public findActivePlayers() { - return this.objects.filter((obj) => obj instanceof Player) as Player[] - } - - public removeInactivePlayers() { - const players = this.findActivePlayers() - for (const player of players) { - const checkTime = getDateMinusMinutes(8) - if (player.lastActionAt.getTime() <= checkTime.getTime()) { - if (player.script) { - continue - } - - const target = this.wagonService.findRandomOutFlag() - const selfDestroyFunc = () => { - this.group.remove(player) - this.removeObject(player) - } - - player.script = new MoveOffScreenAndSelfDestroyScript({ - target, - object: player, - selfDestroyFunc, - }) - } - } - } - - removeDestroyedObject(obj: GameObject) { - if (obj.state === 'DESTROYED') { - this.removeObject(obj) - } - } - - async initPlayer(id: string) { - const instance = new Player({ scene: this, id, x: -100, y: -100 }) - await instance.initFromDB() - await instance.initInventoryFromDB() - - const flag = this.wagonService.findRandomOutFlag() - instance.x = flag.x - instance.y = flag.y - - return instance - } - - public async createPlayer(id: string): Promise { - const player = this.findPlayer(id) - if (!player) { - const instance = await this.initPlayer(id) - this.objects.push(instance) - return instance - } - return player - } - - getTreeToChop() { - // Part 1: Check trees on Wagon Path - const onlyOnPath = this.chunkNow?.objects.filter( - (obj) => - obj instanceof Tree - && obj.state !== 'DESTROYED' - && !obj.isReserved - && obj.isOnWagonPath, - ) - if (onlyOnPath && onlyOnPath.length > 0) { - return this.determineNearestObject( - this.wagonService.wagon, - onlyOnPath, - ) as Tree - } - - // Part 2: Check nearest free tree - const other = this.chunkNow?.objects.filter( - (obj) => - obj instanceof Tree - && obj.state !== 'DESTROYED' - && !obj.isReserved - && obj.isReadyToChop, - ) - if (other && other.length > 0) { - return this.determineNearestObject(this.wagonService.wagon, other) as Tree - } - } - - getStoneToMine() { - // Part 1: Check on Wagon Path - const onlyOnPath = this.chunkNow?.objects.filter( - (obj) => - obj instanceof Stone - && obj.state !== 'DESTROYED' - && !obj.isReserved - && obj.isOnWagonPath, - ) - if (onlyOnPath && onlyOnPath.length > 0) { - return this.determineNearestObject( - this.wagonService.wagon, - onlyOnPath, - ) as Stone - } - - // Part 2: Check nearest free - const other = this.chunkNow?.objects.filter( - (obj) => - obj instanceof Stone && obj.state !== 'DESTROYED' && !obj.isReserved, - ) - if (other && other.length > 0) { - return this.determineNearestObject( - this.wagonService.wagon, - other, - ) as Stone - } - } - - determineNearestObject( - point: { - x: number - y: number - }, - objects: GameObject[], - ) { - let closestObject = objects[0] - let shortestDistance - - for (const object of objects) { - const distance = Route.getDistanceBetween2Points(point, object) - if (!shortestDistance || distance < shortestDistance) { - shortestDistance = distance - closestObject = object - } - } - - return closestObject - } - - initRaiders(count: number) { - for (let i = 0; i < count; i++) { - const flag = this.wagonService.findRandomOutFlag() - - this.objects.push(new Raider({ scene: this, x: flag.x, y: flag.y })) - } - } - - public stopRaid() { - for (const object of this.objects) { - if (object instanceof Raider) { - const target = this.wagonService.findRandomOutFlag() - const selfDestroyFunc = () => { - this.removeObject(object) - } - - object.script = new MoveOffScreenAndSelfDestroyScript({ - target, - object, - selfDestroyFunc, - }) - } - } - } - - getRandomTheme(): IGameChunkTheme { - const themes: IGameChunkTheme[] = [ - 'GREEN', - 'BLUE', - 'STONE', - 'TEAL', - 'VIOLET', - 'TOXIC', - ] - return themes[Math.floor(Math.random() * themes.length)] - } - - findRandomMovementFlag() { - const flags = this.chunkNow?.objects.filter( - (f) => f instanceof Flag && f.type === 'MOVEMENT', - ) - if (!flags) { - return undefined - } - - return flags.length > 0 - ? flags[Math.floor(Math.random() * flags.length)] - : undefined - } - - findRandomEmptyResourceFlag() { - const flags = this.objects.filter( - (f) => f instanceof Flag && f.type === 'RESOURCE' && !f.target, - ) - return flags.length > 0 - ? flags[Math.floor(Math.random() * flags.length)] - : undefined - } -} diff --git a/src/lib/game/scenes/movingScene.ts b/src/lib/game/scenes/movingScene.ts index b012e423..17e0973c 100644 --- a/src/lib/game/scenes/movingScene.ts +++ b/src/lib/game/scenes/movingScene.ts @@ -1,25 +1,27 @@ -import { BaseScene } from './baseScene' -import type { Game } from '$lib/game/types' +import type { Game, GameScene } from '$lib/game/types' interface IMovingSceneOptions { game: Game } -export class MovingScene extends BaseScene { - constructor({ game }: IMovingSceneOptions) { - super({ game }) +export class MovingScene implements GameScene { + game: Game - void this.init() + constructor({ game }: IMovingSceneOptions) { + this.game = game + void this.#init() } - async init() { - const village = this.#initStartingVillage() - const wagonStartPoint = village.getWagonStopPoint() + destroy() {} - this.wagonService.initWagon(wagonStartPoint) - await this.#initGroupPlayers() + async #init() { + const village = this.#initStartingVillage() + const wagonStop = village.wagonStop + if (!wagonStop) { + return + } - // void this.live() + this.game.wagonService.initWagon({ x: wagonStop.x, y: wagonStop.y }) } #initStartingVillage() { @@ -35,26 +37,15 @@ export class MovingScene extends BaseScene { y: Math.round(height / 2 + initialOffsetY), }, } - const village = this.wagonService.routeService.generateRandomVillage({ + const village = this.game.chunkService.generateRandomVillage({ center: area.center, width: area.width, height: area.height, - theme: this.getRandomTheme(), - scene: this, + theme: this.game.chunkService.getRandomTheme(), + game: this.game, }) - this.chunks.push(village) + this.game.chunkService.chunks.push(village) return village } - - async #initGroupPlayers() { - if (!this.group) { - return - } - - for (const player of this.group.players) { - const instance = await this.initPlayer(player.id) - this.objects.push(instance) - } - } } diff --git a/src/lib/game/scenes/services/routeService.ts b/src/lib/game/scenes/services/routeService.ts deleted file mode 100644 index 314b6905..00000000 --- a/src/lib/game/scenes/services/routeService.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Forest, LakeChunk, Village } from '../../chunks' -import { Route } from '../../common' -import { Stone, Tree } from '../../objects' -import { getRandomInRange } from '$lib/random' -import type { - GameScene, - GameSceneService, - IGameChunkTheme, - IGameRoute, -} from '$lib/game/types' - -interface IRouteServiceOptions { - scene: GameScene -} - -export class RouteService implements GameSceneService { - public route: Route | undefined - public scene: GameScene - - constructor({ scene }: IRouteServiceOptions) { - this.scene = scene - } - - update() { - if (!this.route?.flags || this.route.flags.length <= 0) { - if ( - this.scene.eventService.events.find( - (e) => e.type === 'MAIN_QUEST_STARTED', - ) - ) { - return this.finishAdventure() - } - } - - if (this.route) { - for (const flag of this.route.flags) { - void flag.live() - } - } - } - - public getRoute(): IGameRoute | null { - if (!this.route) { - return null - } - - return { - startPoint: this.route.startPoint, - endPoint: this.route.endPoint, - chunks: this.route.chunks, - } - } - - generateAdventure(village: Village, chunks: number) { - const wagonStartPoint = village.getWagonStopPoint() - const villageOutPoint = village.getRandomOutPoint() - - this.route = new Route({ scene: this.scene }) - this.route.addGlobalFlag(wagonStartPoint) - this.route.startPoint = wagonStartPoint - this.route.addChunk(village) - - this.generateChunks({ x: villageOutPoint.x, y: villageOutPoint.y }, chunks) - this.markObjectsAsOnWagonPath(this.route) - } - - generateChunks(startPoint: { x: number, y: number }, amount: number) { - let outPoint = startPoint - - for (let i = 1; i <= amount; i++) { - const chunk = this.generateRandomChunk(outPoint) - outPoint = chunk.getRandomOutPoint() - this.route?.addGlobalFlag(outPoint) - this.route?.addChunk(chunk) - } - - // Generate last chunk - const finalVillageWidth = 3600 - const finalVillage = this.generateRandomVillage({ - center: { x: outPoint.x + finalVillageWidth / 2, y: outPoint.y }, - width: finalVillageWidth, - height: 2000, - theme: this.getRandomTheme(), - scene: this.scene, - }) - this.scene.chunks.push(finalVillage) - const stopPoint = finalVillage.getWagonStopPoint() - this.route?.addGlobalFlag(stopPoint) - this.route?.addChunk(finalVillage) - this.route?.setEndPoint(stopPoint) - } - - generateRandomChunk(startPoint: { x: number, y: number }) { - const random = getRandomInRange(1, 2) - - const width = getRandomInRange(2000, 3000) - const height = getRandomInRange(2500, 3500) - const center = { - x: startPoint.x + width / 2, - y: startPoint.y, - } - - switch (random) { - case 1: - return this.generateRandomForest({ - center, - width, - height, - theme: this.getRandomTheme(), - }) - case 2: - return this.generateRandomLake({ - center, - width, - height, - theme: this.getRandomTheme(), - }) - } - - return this.generateRandomForest({ - center, - width, - height, - theme: this.getRandomTheme(), - }) - } - - markObjectsAsOnWagonPath(route: Route) { - for (const chunk of this.scene.chunks) { - for (const object of chunk.objects) { - if (object instanceof Tree || object instanceof Stone) { - const isOnPath = route.checkIfPointIsOnWagonPath({ - x: object.x, - y: object.y, - }) - if (isOnPath) { - object.isOnWagonPath = true - } - } - } - } - } - - generateRandomVillage({ - center, - width, - height, - theme, - scene, - }: { - center: { x: number, y: number } - width: number - height: number - theme: IGameChunkTheme - scene: GameScene - }) { - return new Village({ width, height, center, theme, scene }) - } - - generateRandomForest({ - center, - width, - height, - theme, - }: { - center: { x: number, y: number } - width: number - height: number - theme: IGameChunkTheme - }) { - const forest = new Forest({ - scene: this.scene, - width, - height, - center, - theme, - }) - this.scene.chunks.push(forest) - return forest - } - - generateRandomLake({ - center, - width, - height, - theme, - }: { - center: { x: number, y: number } - width: number - height: number - theme: IGameChunkTheme - }) { - const lake = new LakeChunk({ - scene: this.scene, - width, - height, - center, - theme, - }) - this.scene.chunks.push(lake) - return lake - } - - getRandomTheme(): IGameChunkTheme { - const themes: IGameChunkTheme[] = [ - 'GREEN', - 'BLUE', - 'STONE', - 'TEAL', - 'VIOLET', - 'TOXIC', - ] - return themes[Math.floor(Math.random() * themes.length)] - } - - finishAdventure() { - console.log('Adventure finished!', new Date()) - this.route = undefined - this.scene.wagonService.wagon.emptyCargo() - this.scene.tradeService.traderIsMovingWithWagon = false - this.scene.tradeService.handleTradeIsOver() - - if (this.scene.chunkNow?.id) { - const id = this.scene.chunkNow.id - this.scene.chunks = this.scene.chunks.filter((chunk) => chunk.id === id) - } - } -} diff --git a/src/lib/game/scenes/services/actionService.ts b/src/lib/game/services/actionService.ts similarity index 78% rename from src/lib/game/scenes/services/actionService.ts rename to src/lib/game/services/actionService.ts index 901ca021..373ca6a0 100644 --- a/src/lib/game/scenes/services/actionService.ts +++ b/src/lib/game/services/actionService.ts @@ -1,17 +1,14 @@ -import type { BaseAction } from '../../actions/baseAction' -import { Village } from '../../chunks' -import { Group } from '../../common' -import { Stone, Tree } from '../../objects' -import type { Warehouse } from '../../objects/buildings/warehouse' -import type { Player } from '../../objects/units' -import { ChopTreeScript } from '../../scripts/chopTreeScript' -import { MineStoneScript } from '../../scripts/mineStoneScript' -import { PlantNewTreeScript } from '../../scripts/plantNewTreeScript' +import type { Player } from '../objects/units/player' +import { ChopTreeScript } from '../scripts/chopTreeScript' +import { MineStoneScript } from '../scripts/mineStoneScript' +import { PlantNewTreeScript } from '../scripts/plantNewTreeScript' import type { - GameScene, - GameSceneService, + Game, + GameObject, GameSceneType, - IGameActionResponse, IGameSceneAction, ItemType, + IGameActionResponse, + IGameSceneAction, + ItemType, } from '$lib/game/types' import { ADMIN_PLAYER_ID, @@ -19,6 +16,13 @@ import { DONATE_URL, GITHUB_REPO_URL, } from '$lib/config' +import type { GameAction } from '$lib/game/actions/interface' +import type { GameService } from '$lib/game/services/interface' +import { TreeObject } from '$lib/game/objects/treeObject' +import { Route } from '$lib/game/common/route' +import { StoneObject } from '$lib/game/objects/stoneObject' +import { VillageChunk } from '$lib/game/services/chunk/villageChunk' +import { Group } from '$lib/game/common/group' interface ICommandWithAction { id: string @@ -26,10 +30,6 @@ interface ICommandWithAction { command: string } -interface IActionServiceOptions { - scene: GameScene -} - export const ANSWER = { OK: { ok: true, @@ -89,18 +89,20 @@ export const ANSWER = { }, } -export class ActionService implements GameSceneService { +export class ActionService implements GameService { possibleCommands!: ICommandWithAction[] possibleActions!: IGameSceneAction[] activeActions!: IGameSceneAction[] - scene: GameScene + game: Game - constructor({ scene }: IActionServiceOptions) { - this.scene = scene + constructor(game: Game) { + this.game = game void this.initActions() } + update() {} + async initActions() { this.possibleActions = [ 'HELP', @@ -132,12 +134,12 @@ export class ActionService implements GameSceneService { } public findDynamicActionByCommand(command: string) { - const quest = this.scene.eventService.findActionByCommandInQuest(command) + const quest = this.game.eventService.findActionByCommandInQuest(command) if (quest) { return quest } - const poll = this.scene.eventService.findActionByCommandInPoll(command) + const poll = this.game.eventService.findActionByCommandInPoll(command) if (poll) { return poll } @@ -148,12 +150,12 @@ export class ActionService implements GameSceneService { playerId: string, params?: string[], ) { - const player = await this.scene.findOrCreatePlayer(playerId) + const player = await this.game.playerService.findOrCreatePlayer(playerId) if (!player) { return ANSWER.NO_PLAYER_ERROR } - this.scene.group.join(player) + this.game.group.join(player) player.updateLastActionAt() if (action === 'SHOW_MESSAGE') { @@ -221,16 +223,16 @@ export class ActionService implements GameSceneService { } public async handleDynamicAction( - action: BaseAction, + action: GameAction, playerId: string, params: string[], ): Promise { - const player = await this.scene.findOrCreatePlayer(playerId) + const player = await this.game.playerService.findOrCreatePlayer(playerId) if (!player) { return ANSWER.NO_PLAYER_ERROR } - this.scene.group.join(player) + this.game.group.join(player) player.updateLastActionAt() const answer = await action.live(player, params) @@ -275,13 +277,13 @@ export class ActionService implements GameSceneService { // First param is raidersCount const raidersCount = params ? Number(params[0]) : 0 - this.scene.eventService.init({ + this.game.eventService.initEvent({ title: 'The raid has started!', description: '', type: 'RAID_STARTED', secondsToEnd: 60 * 5, }) - this.scene.initRaiders(raidersCount) + this.game.initRaiders(raidersCount) // Raider points void player.addRaiderPoints(raidersCount) @@ -309,7 +311,7 @@ export class ActionService implements GameSceneService { return ANSWER.CANT_DO_THIS_NOW_ERROR } - this.scene.wagonService.wagon.emptyFuel() + this.game.wagonService.wagon.emptyFuel() await player.addVillainPoints(1) @@ -355,7 +357,7 @@ export class ActionService implements GameSceneService { await player.addRefuellerPoints(count) - this.scene.wagonService.wagon.refuel(count) + this.game.wagonService.wagon.refuel(count) return { ok: true, @@ -379,7 +381,7 @@ export class ActionService implements GameSceneService { return ANSWER.BUSY_ERROR } - const target = this.scene.getTreeToChop() + const target = this.getTreeToChop() if (!target) { return ANSWER.NO_AVAILABLE_TREE_ERROR } @@ -388,7 +390,7 @@ export class ActionService implements GameSceneService { void player.chopTree() if (!player.target || player.target.state === 'DESTROYED') { player.state = 'IDLE' - if (player.target instanceof Tree) { + if (player.target instanceof TreeObject) { void player.inventory.addOrCreateItem('WOOD', player.target?.resource) } return true @@ -416,7 +418,7 @@ export class ActionService implements GameSceneService { } } - const target = this.scene.getStoneToMine() + const target = this.getStoneToMine() if (!target) { return { ok: false, @@ -428,7 +430,7 @@ export class ActionService implements GameSceneService { void player.mineStone() if (!player.target || player.target.state === 'DESTROYED') { player.state = 'IDLE' - if (player.target instanceof Stone) { + if (player.target instanceof StoneObject) { void player.inventory.addOrCreateItem( 'STONE', player.target?.resource, @@ -459,8 +461,8 @@ export class ActionService implements GameSceneService { } } - if (this.scene.chunkNow instanceof Village) { - const target = this.scene.chunkNow.checkIfNeedToPlantTree() + if (this.game.chunkService.chunk instanceof VillageChunk) { + const target = this.game.chunkService.chunk.checkIfNeedToPlantTree() if (!target) { return { ok: false, @@ -469,8 +471,8 @@ export class ActionService implements GameSceneService { } const plantNewTreeFunc = () => { - if (this.scene.chunkNow instanceof Village) { - this.scene.chunkNow.plantNewTree(target) + if (this.game.chunkService.chunk instanceof VillageChunk) { + this.game.chunkService.chunk.plantNewTree(target) } } @@ -500,7 +502,7 @@ export class ActionService implements GameSceneService { return ANSWER.NO_TARGET_ERROR } - this.scene.eventService.init({ + this.game.eventService.initEvent({ type: 'SCENE_CHANGING_STARTED', title: 'Changing location', description: '', @@ -536,9 +538,9 @@ export class ActionService implements GameSceneService { return ANSWER.NO_TARGET_ERROR } - this.scene.group = new Group() + this.game.group = new Group() - this.scene.eventService.init({ + this.game.eventService.initEvent({ type: 'GROUP_FORM_STARTED', title: 'The group is recruiting!', description: '', @@ -554,7 +556,7 @@ export class ActionService implements GameSceneService { return ANSWER.CANT_DO_THIS_NOW_ERROR } - this.scene.group?.disband() + this.game.group?.disband() return { ok: true, @@ -622,10 +624,7 @@ export class ActionService implements GameSceneService { } } - let warehouse: Warehouse | undefined - if (this.scene.chunkNow instanceof Village) { - warehouse = this.scene.chunkNow.getWarehouse() - } + const warehouse = this.game.chunkService.chunk?.warehouse if (item === 'WOOD') { const isSuccess = await player.inventory.reduceOrDestroyItem(item, amount) @@ -699,7 +698,7 @@ export class ActionService implements GameSceneService { return ANSWER.WRONG_AMOUNT_ERROR } - const status = await this.scene.tradeService.findActiveOfferAndTrade( + const status = await this.game.tradeService.findActiveOfferAndTrade( params[0], amount, player, @@ -723,7 +722,7 @@ export class ActionService implements GameSceneService { private createIdeaAction(player: Player, params: string[] | undefined) { const text = params ? params[0] : '' - this.scene.eventService.init({ + this.game.eventService.initEvent({ title: 'New idea from Twitch Viewer!', description: `${player.userName}: ${text}`, type: 'IDEA_CREATED', @@ -732,4 +731,83 @@ export class ActionService implements GameSceneService { return ANSWER.OK } + + getTreeToChop() { + // Part 1: Check trees on Wagon Path + const onlyOnPath = this.game.children.filter( + (obj) => + obj instanceof TreeObject + && obj.state !== 'DESTROYED' + && !obj.isReserved + && obj.isOnWagonPath, + ) + if (onlyOnPath && onlyOnPath.length > 0) { + return this.determineNearestObject( + this.game.wagonService.wagon, + onlyOnPath, + ) as TreeObject + } + + // Part 2: Check nearest free tree + const other = this.game.children.filter( + (obj) => + obj instanceof TreeObject + && obj.state !== 'DESTROYED' + && !obj.isReserved + && obj.isReadyToChop, + ) + if (other && other.length > 0) { + return this.determineNearestObject(this.game.wagonService.wagon, other) as TreeObject + } + } + + getStoneToMine() { + // Part 1: Check on Wagon Path + const onlyOnPath = this.game.children.filter( + (obj) => + obj instanceof StoneObject + && obj.state !== 'DESTROYED' + && !obj.isReserved + && obj.isOnWagonPath, + ) + if (onlyOnPath && onlyOnPath.length > 0) { + return this.determineNearestObject( + this.game.wagonService.wagon, + onlyOnPath, + ) as StoneObject + } + + // Part 2: Check nearest free + const other = this.game.children.filter( + (obj) => + obj instanceof StoneObject && obj.state !== 'DESTROYED' && !obj.isReserved, + ) + if (other && other.length > 0) { + return this.determineNearestObject( + this.game.wagonService.wagon, + other, + ) as StoneObject + } + } + + determineNearestObject( + point: { + x: number + y: number + }, + objects: GameObject[], + ) { + let closestObject = objects[0] + let shortestDistance + + for (const object of objects) { + const distance = Route.getDistanceBetween2Points(point, object) + if (!shortestDistance || distance < shortestDistance) { + shortestDistance = distance + closestObject = object + } + } + + return closestObject + } } diff --git a/src/lib/game/chunks/gameChunk.ts b/src/lib/game/services/chunk/baseChunk.ts similarity index 50% rename from src/lib/game/chunks/gameChunk.ts rename to src/lib/game/services/chunk/baseChunk.ts index dd2853d7..0cb48fc4 100644 --- a/src/lib/game/chunks/gameChunk.ts +++ b/src/lib/game/services/chunk/baseChunk.ts @@ -1,32 +1,37 @@ import { createId } from '@paralleldrive/cuid2' -import { Area, Tree } from '../objects' import { getRandomInRange } from '$lib/random' import type { - GameObject, - GameScene, - IGameChunk, - IGameChunkTheme, + Game, + GameObjectFlag, + IGameBuildingConstructionArea, + IGameBuildingStore, + IGameBuildingWagonStop, IGameBuildingWarehouse, } from '$lib/game/types' +import { Area } from '$lib/game/objects/area' +import { TreeObject } from '$lib/game/objects/treeObject' +import type { + GameChunk, + IGameChunkTheme, +} from '$lib/game/services/chunk/interface' +import { FlagObject } from '$lib/game/objects/flagObject' interface IGameChunkOptions { - center: IGameChunk['center'] - title: IGameChunk['title'] - type: IGameChunk['type'] + center: GameChunk['center'] + title: GameChunk['title'] + type: GameChunk['type'] theme: IGameChunkTheme width: number height: number - scene: GameScene + game: Game } -export class GameChunk implements IGameChunk { - public id: string - public title: string - public type: IGameChunk['type'] - public center!: IGameChunk['center'] - public area!: Area - - public scene: GameScene - public objects: GameObject[] = [] +export class BaseChunk implements GameChunk { + id: GameChunk['id'] + title: GameChunk['title'] + type: GameChunk['type'] + center!: GameChunk['center'] + area!: GameChunk['area'] + game: Game constructor({ title, @@ -35,19 +40,18 @@ export class GameChunk implements IGameChunk { width, height, center, - scene, + game, }: IGameChunkOptions) { this.id = createId() this.center = center this.title = title this.type = type - - this.scene = scene + this.game = game this.#initArea({ width, height, theme }) } - public live() {} + live() {} #initArea({ width, @@ -68,7 +72,7 @@ export class GameChunk implements IGameChunk { endY: this.center.y + halfHeight, } - this.area = new Area({ scene: this.scene, theme, area }) + this.area = new Area({ game: this.game, theme, area }) } public getRandomPoint() { @@ -78,7 +82,7 @@ export class GameChunk implements IGameChunk { } } - public getRandomOutPoint() { + get randomOutPoint() { const height = this.area.area.endY - this.area.area.startY const offsetFromTop = Math.round(height / 4) @@ -91,7 +95,7 @@ export class GameChunk implements IGameChunk { } } - public checkIfPointIsInArea(point: { x: number, y: number }): boolean { + isPointInArea(point: { x: number, y: number }): boolean { if (point.x >= this.area.area.startX && point.x <= this.area.area.endX) { if (point.y >= this.area.area.startY && point.y <= this.area.area.endY) { return true @@ -101,15 +105,11 @@ export class GameChunk implements IGameChunk { return false } - removeObject(object: GameObject) { - const index = this.objects.indexOf(object) - this.objects.splice(index, 1) - } - - getAvailableTree(): Tree | undefined { - const trees = this.objects.filter( + get availableTree() { + const trees = this.game.children.filter( (obj) => - obj instanceof Tree + obj instanceof TreeObject + && obj.chunkId === this.id && obj.state !== 'DESTROYED' && !obj.isReserved && obj.isReadyToChop, @@ -118,6 +118,32 @@ export class GameChunk implements IGameChunk { return undefined } - return trees[Math.floor(Math.random() * trees.length)] as Tree + return trees[Math.floor(Math.random() * trees.length)] as TreeObject + } + + get randomMovementFlag() { + const flags = this.game.children.filter( + (f) => f instanceof FlagObject && f.chunkId === this.id && f.variant === 'MOVEMENT', + ) + + return flags.length > 0 + ? flags[Math.floor(Math.random() * flags.length)] as GameObjectFlag + : undefined + } + + get warehouse() { + return this.game.children.find((b) => b.type === 'WAREHOUSE') as IGameBuildingWarehouse | undefined + } + + get store() { + return this.game.children.find((b) => b.type === 'STORE') as IGameBuildingStore | undefined + } + + get constructionArea() { + return this.game.children.find((b) => b.type === 'CONSTRUCTION_AREA') as IGameBuildingConstructionArea | undefined + } + + get wagonStop(): IGameBuildingWagonStop | undefined { + return this.game.children.find((b) => b.type === 'WAGON_STOP') as IGameBuildingWagonStop | undefined } } diff --git a/src/lib/game/services/chunk/chunkService.ts b/src/lib/game/services/chunk/chunkService.ts new file mode 100644 index 00000000..a6b7eacd --- /dev/null +++ b/src/lib/game/services/chunk/chunkService.ts @@ -0,0 +1,195 @@ +import type { Game } from '$lib/game/types' +import type { + GameChunk, + GameChunkService, IGameChunkTheme, +} from '$lib/game/services/chunk/interface' +import { VillageChunk } from '$lib/game/services/chunk/villageChunk' +import { ForestChunk } from '$lib/game/services/chunk/forestChunk' +import { LakeChunk } from '$lib/game/services/chunk/lakeChunk' +import { getRandomInRange } from '$lib/random' + +export class ChunkService implements GameChunkService { + chunks: GameChunk[] = [] + chunkNowId!: string + game: Game + + constructor(game: Game) { + this.game = game + } + + update() { + for (const chunk of this.chunks) { + const isWagonOnThisChunk = chunk.isPointInArea({ + x: this.game.wagonService.wagon.x, + y: this.game.wagonService.wagon.y, + }) + if (isWagonOnThisChunk) { + this.chunkNowId = chunk.id + } + + chunk.live() + } + } + + get chunk() { + return this.chunks.find((chunk) => chunk.id === this.chunkNowId) + } + + removeChunk(chunk: GameChunk) { + this.chunks = this.chunks.filter((c) => c.id !== chunk.id) + } + + removeAllOutsideChunks() { + const id = this.chunk?.id + if (id) { + this.chunks = this.chunks.filter((c) => c.id !== id) + } + } + + generateChunks(startPoint: { x: number, y: number }, amount: number) { + let outPoint = startPoint + + for (let i = 1; i <= amount; i++) { + const chunk = this.generateRandomChunk(outPoint) + outPoint = chunk.randomOutPoint + this.game.routeService.route?.addGlobalFlag(outPoint) + this.game.routeService.addChunk(chunk) + } + + // Generate last chunk + const finalVillageWidth = 3600 + const finalVillage = this.generateRandomVillage({ + center: { x: outPoint.x + finalVillageWidth / 2, y: outPoint.y }, + width: finalVillageWidth, + height: 2000, + theme: this.getRandomTheme(), + game: this.game, + }) + this.chunks.push(finalVillage) + + const stopPoint = finalVillage.wagonStop + if (stopPoint) { + this.game.routeService.route?.addGlobalFlag({ + x: stopPoint.x, + y: stopPoint.y, + }) + this.game.routeService.addChunk(finalVillage) + this.game.routeService.route?.setEndPoint({ + x: stopPoint.x, + y: stopPoint.y, + }) + } + } + + generateRandomChunk(startPoint: { x: number, y: number }) { + const random = getRandomInRange(1, 2) + + const width = getRandomInRange(2000, 3000) + const height = getRandomInRange(2500, 3500) + const center = { + x: startPoint.x + width / 2, + y: startPoint.y, + } + + switch (random) { + case 1: + return this.generateRandomForest({ + center, + width, + height, + theme: this.getRandomTheme(), + }) + case 2: + return this.generateRandomLake({ + center, + width, + height, + theme: this.getRandomTheme(), + }) + } + + return this.generateRandomForest({ + center, + width, + height, + theme: this.getRandomTheme(), + }) + } + + getRandomTheme(): IGameChunkTheme { + const themes: IGameChunkTheme[] = [ + 'GREEN', + 'BLUE', + 'STONE', + 'TEAL', + 'VIOLET', + 'TOXIC', + ] + return themes[Math.floor(Math.random() * themes.length)] + } + + generateRandomVillage({ + center, + width, + height, + theme, + game, + }: { + center: { x: number, y: number } + width: number + height: number + theme: IGameChunkTheme + game: Game + }): VillageChunk { + const chunk = new VillageChunk({ width, height, center, theme, game }) + this.chunks.push(chunk) + + return chunk + } + + generateRandomForest({ + center, + width, + height, + theme, + }: { + center: { x: number, y: number } + width: number + height: number + theme: IGameChunkTheme + }) { + const forest = new ForestChunk({ + game: this.game, + width, + height, + center, + theme, + }) + this.chunks.push(forest) + + return forest + } + + generateRandomLake({ + center, + width, + height, + theme, + }: { + center: { x: number, y: number } + width: number + height: number + theme: IGameChunkTheme + }) { + const lake = new LakeChunk({ + game: this.game, + width, + height, + center, + theme, + }) + this.chunks.push(lake) + + return lake + } +} diff --git a/src/lib/game/services/chunk/forestChunk.ts b/src/lib/game/services/chunk/forestChunk.ts new file mode 100644 index 00000000..7534289c --- /dev/null +++ b/src/lib/game/services/chunk/forestChunk.ts @@ -0,0 +1,67 @@ +import { BaseChunk } from './baseChunk' +import { getRandomInRange } from '$lib/random' +import type { + Game, +} from '$lib/game/types' +import { TreeObject } from '$lib/game/objects/treeObject' +import { StoneObject } from '$lib/game/objects/stoneObject' +import type { + IGameChunkTheme, + IGameForestChunk, +} from '$lib/game/services/chunk/interface' + +interface ForestChunkOptions { + center: IGameForestChunk['center'] + width: number + height: number + theme: IGameChunkTheme + game: Game +} + +export class ForestChunk extends BaseChunk implements IGameForestChunk { + constructor({ width, height, center, theme, game }: ForestChunkOptions) { + super({ + title: 'Grand Wood', + type: 'FOREST', + width, + height, + center, + theme, + game, + }) + + const treesToPrepare = Math.round( + (this.area.area.endX - this.area.area.startX) / 10, + ) + this.#initTrees(treesToPrepare) + this.#initStones(3) + } + + #initTrees(count: number) { + for (let i = 0; i < count; i++) { + const point = this.getRandomPoint() + const size = getRandomInRange(75, 90) + new TreeObject({ + game: this.game, + x: point.x, + y: point.y, + size, + resource: 1, + health: 20, + theme: this.area.theme, + }).init() + } + } + + #initStones(count: number) { + for (let i = 0; i < count; i++) { + const point = this.getRandomPoint() + new StoneObject({ + game: this.game, + x: point.x, + y: point.y, + resource: 1, + }).init() + } + } +} diff --git a/src/lib/game/services/chunk/interface.ts b/src/lib/game/services/chunk/interface.ts new file mode 100644 index 00000000..fb4c8013 --- /dev/null +++ b/src/lib/game/services/chunk/interface.ts @@ -0,0 +1,61 @@ +import type { GameService } from '$lib/game/services/interface' +import type { + Game, + GameObjectFlag, + GameObjectTree, + IGameBuildingConstructionArea, + IGameBuildingStore, + IGameBuildingWagonStop, + IGameBuildingWarehouse, IGameObjectArea, +} from '$lib/game/types' + +export interface GameChunkService extends GameService { + chunkNowId: string + chunk: GameChunk | undefined + chunks: GameChunk[] + removeChunk: (chunk: GameChunk) => void + removeAllOutsideChunks: () => void + generateChunks: (startPoint: { x: number, y: number }, amount: number) => void + generateRandomVillage: (data: { + center: { x: number, y: number } + width: number + height: number + theme: IGameChunkTheme + game: Game + }) => IGameVillageChunk + getRandomTheme: () => IGameChunkTheme +} + +export interface GameChunk { + id: string + title: string + type: 'VILLAGE' | 'FOREST' | 'LAKE' + center: { + x: number + y: number + } + area: IGameObjectArea + warehouse: IGameBuildingWarehouse | undefined + store: IGameBuildingStore | undefined + constructionArea: IGameBuildingConstructionArea | undefined + wagonStop: IGameBuildingWagonStop | undefined + availableTree: GameObjectTree | undefined + randomMovementFlag: GameObjectFlag | undefined + randomOutPoint: { x: number, y: number } + live: () => void + isPointInArea: (point: { x: number, y: number }) => boolean +} + +export type IGameChunkTheme = + | 'GREEN' + | 'TOXIC' + | 'STONE' + | 'TEAL' + | 'BLUE' + | 'VIOLET' + +export interface IGameVillageChunk extends GameChunk {} + +export interface IGameForestChunk extends GameChunk {} + +export interface IGameLakeChunk extends GameChunk {} diff --git a/src/lib/game/chunks/lake.ts b/src/lib/game/services/chunk/lakeChunk.ts similarity index 52% rename from src/lib/game/chunks/lake.ts rename to src/lib/game/services/chunk/lakeChunk.ts index 421b6060..dd6f5c24 100644 --- a/src/lib/game/chunks/lake.ts +++ b/src/lib/game/services/chunk/lakeChunk.ts @@ -1,24 +1,28 @@ -import { Lake, Stone, Tree } from '../objects' -import { GameChunk } from './gameChunk' +import { BaseChunk } from './baseChunk' import { getRandomInRange } from '$lib/random' import type { - GameScene, + Game, +} from '$lib/game/types' +import { Lake } from '$lib/game/objects/lake' +import { TreeObject } from '$lib/game/objects/treeObject' +import { StoneObject } from '$lib/game/objects/stoneObject' +import type { IGameChunkTheme, IGameLakeChunk, -} from '$lib/game/types' +} from '$lib/game/services/chunk/interface' interface ILakeOptions { - scene: GameScene + game: Game center: IGameLakeChunk['center'] width: number height: number theme: IGameChunkTheme } -export class LakeChunk extends GameChunk implements IGameLakeChunk { - constructor({ scene, width, height, center, theme }: ILakeOptions) { +export class LakeChunk extends BaseChunk implements IGameLakeChunk { + constructor({ game, width, height, center, theme }: ILakeOptions) { super({ - scene, + game, width, height, center, @@ -30,60 +34,49 @@ export class LakeChunk extends GameChunk implements IGameLakeChunk { const treesToPrepare = Math.round( (this.area.area.endX - this.area.area.startX) / 30, ) - this.initTrees(treesToPrepare) - this.initStones(3) - this.initLake() + this.#initTrees(treesToPrepare) + this.#initStones(3) + this.#initLake() } - live() { - super.live() - - for (const obj of this.objects) { - void obj.live() - } - } - - initLake() { - const lake = new Lake({ - scene: this.scene, + #initLake() { + new Lake({ + game: this.game, x: this.center.x - 100, y: this.center.y + 400, - }) - const lake2 = new Lake({ - scene: this.scene, + }).init() + new Lake({ + game: this.game, x: this.center.x - 600, y: this.center.y + 500, - }) - this.objects.push(lake, lake2) + }).init() } - initTrees(count: number) { + #initTrees(count: number) { for (let i = 0; i < count; i++) { const point = this.getRandomPoint() const size = getRandomInRange(75, 90) - const tree = new Tree({ - scene: this.scene, + new TreeObject({ + game: this.game, x: point.x, y: point.y, size, resource: 1, health: 20, - variant: this.area.theme, - }) - this.objects.push(tree) + theme: this.area.theme, + }).init() } } - initStones(count: number) { + #initStones(count: number) { for (let i = 0; i < count; i++) { const point = this.getRandomPoint() - const stone = new Stone({ - scene: this.scene, + new StoneObject({ + game: this.game, x: point.x, y: point.y, resource: 1, - }) - this.objects.push(stone) + }).init() } } } diff --git a/src/lib/game/chunks/village.ts b/src/lib/game/services/chunk/villageChunk.ts similarity index 56% rename from src/lib/game/chunks/village.ts rename to src/lib/game/services/chunk/villageChunk.ts index 0bdc9d6a..213603d8 100644 --- a/src/lib/game/chunks/village.ts +++ b/src/lib/game/services/chunk/villageChunk.ts @@ -1,34 +1,40 @@ -import { Flag, Stone, Tree } from '../objects' -import { Campfire } from '../objects/buildings/campfire' -import { ConstructionArea } from '../objects/buildings/constructionArea' -import { Store } from '../objects/buildings/store' -import { WagonStop } from '../objects/buildings/wagonStop' -import { Warehouse } from '../objects/buildings/warehouse' -import { Courier, Farmer } from '../objects/units' -import { BuildScript } from '../scripts/buildScript' -import { ChopTreeScript } from '../scripts/chopTreeScript' -import { MoveToTargetScript } from '../scripts/moveToTargetScript' -import { PlaceItemInWarehouseScript } from '../scripts/placeItemInWarehouseScript' -import { PlantNewTreeScript } from '../scripts/plantNewTreeScript' -import { GameChunk } from './gameChunk' +import { Campfire } from '../../objects/buildings/campfire' +import { ConstructionArea } from '../../objects/buildings/constructionArea' +import { Store } from '../../objects/buildings/store' +import { WagonStop } from '../../objects/buildings/wagonStop' +import { Warehouse } from '../../objects/buildings/warehouse' +import { BuildScript } from '../../scripts/buildScript' +import { ChopTreeScript } from '../../scripts/chopTreeScript' +import { MoveToTargetScript } from '../../scripts/moveToTargetScript' +import { PlaceItemInWarehouseScript } from '../../scripts/placeItemInWarehouseScript' +import { PlantNewTreeScript } from '../../scripts/plantNewTreeScript' +import { BaseChunk } from './baseChunk' import { getRandomInRange } from '$lib/random' import type { + Game, GameObjectFlag, - GameScene, - IGameChunkTheme, IGameVillageChunk, } from '$lib/game/types' +import { Farmer } from '$lib/game/objects/units/farmer' +import { Courier } from '$lib/game/objects/units/courier' +import { TreeObject } from '$lib/game/objects/treeObject' +import { FlagObject } from '$lib/game/objects/flagObject' +import { StoneObject } from '$lib/game/objects/stoneObject' +import type { + IGameChunkTheme, + IGameVillageChunk, +} from '$lib/game/services/chunk/interface' interface IVillageOptions { - scene: GameScene + game: Game width: number height: number center: IGameVillageChunk['center'] theme: IGameChunkTheme } -export class Village extends GameChunk implements IGameVillageChunk { - constructor({ width, height, center, theme, scene }: IVillageOptions) { - super({ title: '', type: 'VILLAGE', theme, width, height, center, scene }) +export class VillageChunk extends BaseChunk implements IGameVillageChunk { + constructor({ width, height, center, theme, game }: IVillageOptions) { + super({ title: '', type: 'VILLAGE', theme, width, height, center, game }) this.title = this.getRandomTitle() @@ -45,7 +51,8 @@ export class Village extends GameChunk implements IGameVillageChunk { live() { super.live() - for (const object of this.objects) { + const objectsOfThisVillage = this.game.children.filter((obj) => obj.chunkId === this.id) + for (const object of objectsOfThisVillage) { if (object instanceof Farmer && !object.script) { this.addTaskToFarmer(object) continue @@ -64,12 +71,12 @@ export class Village extends GameChunk implements IGameVillageChunk { } // Need to build Store - const warehouse = this.getWarehouse() - const store = this.getStore() - const wood = warehouse?.getItemByType('WOOD') + const warehouse = this.game.chunkService.chunk?.warehouse + const store = this.game.chunkService.chunk?.store + const wood = warehouse?.inventory.items.find((item) => item.type === 'WOOD') if (wood?.amount && wood.amount >= 25 && !store) { // Let's build! - const target = this.getConstructionArea() + const target = this.game.chunkService.chunk?.constructionArea if (!target) { return } @@ -92,7 +99,7 @@ export class Village extends GameChunk implements IGameVillageChunk { // If unit have smth in inventory const item = object.inventory.checkIfAlreadyHaveItem('WOOD') if (item) { - const target = this.getWarehouse() + const target = this.game.chunkService.chunk?.warehouse if (!target) { return } @@ -112,14 +119,12 @@ export class Village extends GameChunk implements IGameVillageChunk { return } - // If there is an available tree - const availableTree = this.getAvailableTree() - if (availableTree) { + if (this.availableTree) { const chopTreeFunc = (): boolean => { object.chopTree() if (!object.target || object.target.state === 'DESTROYED') { object.state = 'IDLE' - if (object.target instanceof Tree) { + if (object.target instanceof TreeObject) { void object.inventory.addOrCreateItem( 'WOOD', object.target?.resource, @@ -132,7 +137,7 @@ export class Village extends GameChunk implements IGameVillageChunk { object.script = new ChopTreeScript({ object, - target: availableTree, + target: this.availableTree, chopTreeFunc, }) @@ -178,57 +183,55 @@ export class Village extends GameChunk implements IGameVillageChunk { } } - initFlag(type: GameObjectFlag['type']) { + initFlag(variant: GameObjectFlag['variant']) { const randomPoint = this.getRandomPoint() - this.objects.push( - new Flag({ - scene: this.scene, - type, - x: randomPoint.x, - y: randomPoint.y, - }), - ) + new FlagObject({ + game: this.game, + chunkId: this.id, + variant, + x: randomPoint.x, + y: randomPoint.y, + }).init() } - initFlags(type: GameObjectFlag['type'], count: number) { + initFlags(variant: GameObjectFlag['variant'], count: number) { for (let i = 0; i < count; i++) { - this.initFlag(type) + this.initFlag(variant) } } initTrees(count: number) { for (let i = 0; i < count; i++) { - const flag = this.getRandomEmptyResourceFlagInVillage() + const flag = this.#getRandomEmptyResourceFlagInVillage() if (flag) { const size = getRandomInRange(65, 85) - const tree = new Tree({ - scene: this.scene, + const tree = new TreeObject({ + game: this.game, x: flag.x, y: flag.y, size, resource: 1, health: 20, - variant: this.area.theme, + theme: this.area.theme, }) flag.target = tree - - this.objects.push(tree) + tree.init() } } } initStones(count: number) { for (let i = 0; i < count; i++) { - const flag = this.getRandomEmptyResourceFlagInVillage() + const flag = this.#getRandomEmptyResourceFlagInVillage() if (flag) { - const stone = new Stone({ - scene: this.scene, + const stone = new StoneObject({ + game: this.game, x: flag.x, y: flag.y, resource: 1, }) flag.target = stone - this.objects.push(stone) + stone.init() } } } @@ -236,122 +239,90 @@ export class Village extends GameChunk implements IGameVillageChunk { initCourier(count = 1) { for (let i = 0; i < count; i++) { const randomPoint = this.getRandomPoint() - const courier = new Courier({ - scene: this.scene, + new Courier({ + game: this.game, x: randomPoint.x, y: randomPoint.y, - }) - - this.objects.push(courier) + }).init() } } initFarmer(count = 1) { for (let i = 0; i < count; i++) { const randomPoint = this.getRandomPoint() - const farmer = new Farmer({ - scene: this.scene, + new Farmer({ + game: this.game, x: randomPoint.x, y: randomPoint.y, - }) - - this.objects.push(farmer) + }).init() } } initBuildings() { - const campfire = new Campfire({ - scene: this.scene, + new Campfire({ + game: this.game, x: this.center.x, y: this.center.y, - }) + }).init() - const warehouse = new Warehouse({ - scene: this.scene, + new Warehouse({ + game: this.game, x: this.center.x + 270, y: this.center.y - 150, - }) + }).init() - const wagonStop = new WagonStop({ - scene: this.scene, + new WagonStop({ + game: this.game, x: this.center.x - 780, y: this.center.y + 280, - }) + }).init() - const constructionArea = new ConstructionArea({ - scene: this.scene, + new ConstructionArea({ + game: this.game, x: this.center.x + 600, y: this.center.y + 250, - }) - - this.objects.push(campfire, warehouse, wagonStop, constructionArea) + }).init() } buildStore() { - const constructionArea = this.getConstructionArea() + const constructionArea = this.game.chunkService.chunk?.constructionArea if (!constructionArea) { return } constructionArea.state = 'DESTROYED' - this.removeObject(constructionArea) - this.objects.push( - new Store({ - scene: this.scene, - x: constructionArea.x, - y: constructionArea.y, - }), - ) - } - - getWarehouse() { - return this.objects.find((b) => b instanceof Warehouse) as - | Warehouse - | undefined - } - - getStore() { - return this.objects.find((b) => b instanceof Store) as Store | undefined - } - - getConstructionArea() { - return this.objects.find((b) => b instanceof ConstructionArea) as - | ConstructionArea - | undefined - } - - public getWagonStopPoint() { - for (const object of this.objects) { - if (object instanceof WagonStop) { - return { x: object.x, y: object.y } - } - } - return { x: 500, y: 500 } + this.game.removeObject(constructionArea) + new Store({ + game: this.game, + x: constructionArea.x, + y: constructionArea.y, + }).init() } - getRandomEmptyResourceFlagInVillage() { - const flags = this.objects.filter( + #getRandomEmptyResourceFlagInVillage() { + const flags = this.game.children.filter( (f) => - f instanceof Flag - && f.type === 'RESOURCE' + f instanceof FlagObject + && f.chunkId === this.id + && f.variant === 'RESOURCE' && !f.target && !f.isReserved, ) return flags.length > 0 - ? (flags[Math.floor(Math.random() * flags.length)] as Flag) + ? (flags[Math.floor(Math.random() * flags.length)] as FlagObject) : undefined } - getResourceFlagInVillageAmount() { - return this.objects.filter( - (f) => f instanceof Flag && f.type === 'RESOURCE', + #getResourceFlagInVillageAmount() { + return this.game.children.filter( + (f) => f instanceof FlagObject && f.chunkId === this.id && f.variant === 'RESOURCE', ).length } getRandomMovementFlagInVillage() { - const flags = this.objects.filter( - (f) => f instanceof Flag && f.type === 'MOVEMENT', + const flags = this.game.children.filter( + (f) => f instanceof FlagObject && f.chunkId === this.id && f.variant === 'MOVEMENT', ) return flags.length > 0 ? flags[Math.floor(Math.random() * flags.length)] @@ -375,38 +346,38 @@ export class Village extends GameChunk implements IGameVillageChunk { } checkIfNeedToPlantTree() { - const treesNow = this.objects.filter( - (t) => t instanceof Tree && t.state !== 'DESTROYED', + const treesNow = this.game.children.filter( + (t) => t instanceof TreeObject && t.chunkId === this.id && t.state !== 'DESTROYED', ) if (treesNow.length < 40) { - return this.getRandomEmptyResourceFlagInVillage() + return this.#getRandomEmptyResourceFlagInVillage() } } - plantNewTree(flag: Flag) { - const tree = new Tree({ - scene: this.scene, + plantNewTree(flag: FlagObject) { + const tree = new TreeObject({ + game: this.game, x: flag.x, y: flag.y, resource: 1, size: 12, health: 20, - variant: this.area.theme, + theme: this.area.theme, }) flag.target = tree flag.isReserved = false - this.objects.push(tree) + tree.init() } getTreesAmount() { - return this.objects.filter( - (obj) => obj instanceof Tree && obj.state !== 'DESTROYED', + return this.game.children.filter( + (obj) => obj instanceof TreeObject && obj.chunkId === this.id && obj.state !== 'DESTROYED', ).length } checkIfThereAreNotEnoughTrees() { - const max = this.getResourceFlagInVillageAmount() + const max = this.#getResourceFlagInVillageAmount() const now = this.getTreesAmount() return now < max / 3 diff --git a/src/lib/game/common/event.ts b/src/lib/game/services/event/event.ts similarity index 93% rename from src/lib/game/common/event.ts rename to src/lib/game/services/event/event.ts index 8c664aaa..5ebb731e 100644 --- a/src/lib/game/common/event.ts +++ b/src/lib/game/services/event/event.ts @@ -1,6 +1,7 @@ import { createId } from '@paralleldrive/cuid2' import { getDatePlusSeconds } from '$lib/date' -import type { GameSceneType, IGameEvent } from '$lib/game/types' +import type { GameSceneType } from '$lib/game/types' +import type { IGameEvent } from '$lib/game/services/interface' interface IEventOptions { title: IGameEvent['title'] diff --git a/src/lib/game/scenes/services/eventService.ts b/src/lib/game/services/event/eventService.ts similarity index 72% rename from src/lib/game/scenes/services/eventService.ts rename to src/lib/game/services/event/eventService.ts index 5289206c..e3ae159e 100644 --- a/src/lib/game/scenes/services/eventService.ts +++ b/src/lib/game/services/event/eventService.ts @@ -1,30 +1,26 @@ -import type { BaseAction } from '../../actions/baseAction' -import { Village } from '../../chunks' -import { Event } from '../../common' import { PollService } from './pollService' import { QuestService } from './questService' import type { - GameScene, - GameSceneService, - GameSceneType, - IGameEvent, IGamePoll, IGameQuest, IGameQuestTask, + Game, + GameSceneType, IGamePoll, IGameQuest, IGameQuestTask, } from '$lib/game/types' +import { Event } from '$lib/game/services/event/event' +import { VillageChunk } from '$lib/game/services/chunk/villageChunk' +import type { + GameEventService, IGameEvent, +} from '$lib/game/services/interface' -interface IEventServiceOptions { - scene: GameScene -} - -export class EventService implements GameSceneService { +export class EventService implements GameEventService { events: Event[] = [] questService: QuestService pollService: PollService - scene: GameScene + game: Game - constructor({ scene }: IEventServiceOptions) { - this.scene = scene + constructor(game: Game) { + this.game = game - this.questService = new QuestService({ scene }) - this.pollService = new PollService({ scene }) + this.questService = new QuestService(this.game) + this.pollService = new PollService(this.game) } update() { @@ -44,7 +40,7 @@ export class EventService implements GameSceneService { this.questService.update() } - public init({ + initEvent({ title, description, type, @@ -73,22 +69,9 @@ export class EventService implements GameSceneService { quest, offers, }) - this.events.push(event) - } - public getEvents(): IGameEvent[] { - return this.events.map((event) => ({ - id: event.id, - title: event.title, - description: event.description, - type: event.type, - status: event.status, - endsAt: event.endsAt, - poll: this.preparePollData(event.poll), - quest: this.prepareQuestData(event.quest), - offers: event.offers, - })) + return event } prepareQuestData(quest: IGameQuest | undefined) { @@ -142,7 +125,7 @@ export class EventService implements GameSceneService { (q) => q.action?.command === command, ) if (task?.action) { - return task.action as BaseAction + return task.action } } } @@ -151,26 +134,26 @@ export class EventService implements GameSceneService { public findActionByCommandInPoll(command: string) { for (const event of this.events) { if (event.poll?.action && event.poll.action.command === command) { - return event.poll?.action as BaseAction + return event.poll?.action } } } private handleEnding(event: Event) { if (event.type === 'SCENE_CHANGING_STARTED' && event.scene) { - this.scene.game.initScene(event.scene) + this.game.initScene(event.scene) } if (event.type === 'GROUP_FORM_STARTED' && event.scene) { - this.scene.game.initScene(event.scene) + this.game.initScene(event.scene) } if (event.type === 'RAID_STARTED') { - this.scene.stopRaid() + this.game.stopRaid() } if (event.type === 'TRADE_STARTED') { - this.scene.tradeService.handleTradeIsOver() + this.game.tradeService.handleTradeIsOver() } if (event.type === 'VOTING_FOR_NEW_MAIN_QUEST_STARTED') { - this.scene.tradeService.handleTradeIsOver() + this.game.tradeService.handleTradeIsOver() } } @@ -189,7 +172,7 @@ export class EventService implements GameSceneService { const updateProgress1: IGameQuestTask['updateProgress'] = () => { if ( - !this.scene.routeService.route?.flags + !this.game.routeService.route?.flags && this.events.find((e) => e.type === 'MAIN_QUEST_STARTED') ) { return { @@ -198,7 +181,7 @@ export class EventService implements GameSceneService { } const items - = this.scene.wagonService.wagon.cargo?.checkIfAlreadyHaveItem('WOOD') + = this.game.wagonService.wagon.cargo?.checkIfAlreadyHaveItem('WOOD') if (!items) { return { status: 'FAILED', @@ -221,7 +204,7 @@ export class EventService implements GameSceneService { }), ] - this.init({ + this.initEvent({ title: 'Journey', description: '', type: 'MAIN_QUEST_STARTED', @@ -234,16 +217,16 @@ export class EventService implements GameSceneService { }) // Cargo - this.scene.wagonService.wagon.setCargo() + this.game.wagonService.wagon.setCargo() - if (this.scene.chunkNow instanceof Village) { - this.scene.routeService.generateAdventure( - this.scene.chunkNow, + if (this.game.chunkService.chunk instanceof VillageChunk) { + this.game.routeService.generateAdventure( + this.game.chunkService.chunk, event.quest.conditions.chunks ?? 3, ) } - this.scene.tradeService.traderIsMovingWithWagon = true + this.game.tradeService.traderIsMovingWithWagon = true this.destroyAllEventsWithPoll() } diff --git a/src/lib/game/scenes/services/pollService.ts b/src/lib/game/services/event/pollService.ts similarity index 54% rename from src/lib/game/scenes/services/pollService.ts rename to src/lib/game/services/event/pollService.ts index c6a5a5d8..ce635533 100644 --- a/src/lib/game/scenes/services/pollService.ts +++ b/src/lib/game/services/event/pollService.ts @@ -1,24 +1,18 @@ -import type { Player } from '../../objects/units' import type { - GameScene, - GameSceneService, - IGameObjectPlayer, - IGamePoll, + Game, + GameObjectPlayer, IGamePoll, } from '$lib/game/types' +import type { GameService } from '$lib/game/services/interface' -interface IPollServiceOptions { - scene: GameScene -} - -export class PollService implements GameSceneService { - scene: GameScene +export class PollService implements GameService { + game: Game - constructor({ scene }: IPollServiceOptions) { - this.scene = scene + constructor(game: Game) { + this.game = game } update() { - for (const event of this.scene.eventService.events) { + for (const event of this.game.eventService.events) { if (!event.poll || event.poll.status !== 'ACTIVE') { continue } @@ -29,8 +23,8 @@ export class PollService implements GameSceneService { } } - public findActivePollAndVote(pollId: string, player: Player) { - for (const event of this.scene.eventService.events) { + public findActivePollAndVote(pollId: string, player: GameObjectPlayer) { + for (const event of this.game.eventService.events) { if (event.poll && event.poll?.id === pollId) { const voted = this.vote(event.poll, player) if (!voted) { @@ -43,7 +37,7 @@ export class PollService implements GameSceneService { return 'POLL_NOT_FOUND' } - private vote(poll: IGamePoll, player: IGameObjectPlayer): boolean { + private vote(poll: IGamePoll, player: GameObjectPlayer): boolean { if (poll.votes.find((v) => v.id === player.id)) { return false } diff --git a/src/lib/game/scenes/services/questService.ts b/src/lib/game/services/event/questService.ts similarity index 72% rename from src/lib/game/scenes/services/questService.ts rename to src/lib/game/services/event/questService.ts index ae363cd7..8284c01c 100644 --- a/src/lib/game/scenes/services/questService.ts +++ b/src/lib/game/services/event/questService.ts @@ -1,34 +1,29 @@ import { createId } from '@paralleldrive/cuid2' import { DonateWoodToVillageAction } from '../../actions/donateWoodToVillageAction' import { PlantTreeAction } from '../../actions/plantTreeAction' -import { Village } from '../../chunks' import { NoTradingPostQuest } from '../../quests/noTradingPostQuest' import { TreesAreRunningOutQuest } from '../../quests/treesAreRunningOutQuest' import type { - GameSceneService, - IGameQuest, - IGameQuestTask, IGameQuestTaskFunc, + Game, IGameQuest, IGameQuestTask, IGameQuestTaskFunc, } from '$lib/game/types' +import { VillageChunk } from '$lib/game/services/chunk/villageChunk' +import type { GameService } from '$lib/game/services/interface' -interface IQuestServiceOptions { - scene: GameScene -} - -export class QuestService implements GameSceneService { - scene: GameScene +export class QuestService implements GameService { + game: Game - constructor({ scene }: IQuestServiceOptions) { - this.scene = scene + constructor(game: Game) { + this.game = game } update() { this.updateAndFinishActiveQuests() - if (this.scene.chunkNow instanceof Village) { + if (this.game.chunkService.chunk instanceof VillageChunk) { this.generateNewSideQuest() } - for (const event of this.scene.eventService.events) { + for (const event of this.game.eventService.events) { if (!event.quest) { continue } @@ -96,7 +91,7 @@ export class QuestService implements GameSceneService { } private updateAndFinishActiveQuests() { - for (const event of this.scene.eventService.events) { + for (const event of this.game.eventService.events) { if (!event.quest || event.quest.status !== 'ACTIVE') { continue } @@ -104,8 +99,8 @@ export class QuestService implements GameSceneService { // Tasks done? if (!event.quest.tasks.find((t) => t.status === 'ACTIVE')) { // - this.scene.wagonService.wagon.emptyCargo() - this.scene.tradeService.traderIsMovingWithWagon = false + this.game.wagonService.wagon.emptyCargo() + this.game.tradeService.traderIsMovingWithWagon = false if (!event.quest.tasks.find((t) => t.status === 'FAILED')) { // Reward @@ -119,7 +114,7 @@ export class QuestService implements GameSceneService { } private generateSecondSideQuest() { - const sideQuests = this.scene.eventService.events.filter( + const sideQuests = this.game.eventService.events.filter( (e) => e.type === 'SIDE_QUEST_STARTED', ) if (sideQuests.length >= 1) { @@ -127,8 +122,8 @@ export class QuestService implements GameSceneService { } const taskUpdateFunc1: IGameQuestTask['updateProgress'] = () => { - if (this.scene.chunkNow instanceof Village) { - const treesAmount = this.scene.chunkNow.getTreesAmount() + if (this.game.chunkService.chunk instanceof VillageChunk) { + const treesAmount = this.game.chunkService.chunk.getTreesAmount() if (treesAmount >= 30) { return { status: 'SUCCESS', @@ -147,7 +142,7 @@ export class QuestService implements GameSceneService { } } - const taskAction1 = new PlantTreeAction({ scene: this.scene }) + const taskAction1 = new PlantTreeAction({ game: this.game }) const quest = new TreesAreRunningOutQuest({ creatorId: '1', @@ -155,7 +150,7 @@ export class QuestService implements GameSceneService { taskAction1, }) - this.scene.eventService.init({ + this.game.eventService.initEvent({ type: 'SIDE_QUEST_STARTED', title: quest.title, description: quest.description, @@ -165,14 +160,14 @@ export class QuestService implements GameSceneService { } private generateNewSideQuest() { - if (!this.scene.chunkNow) { + if (!this.game.chunkService.chunk) { return } - if (this.scene.chunkNow instanceof Village) { - const store = this.scene.chunkNow.getStore() + if (this.game.chunkService.chunk instanceof VillageChunk) { + const store = this.game.chunkService.chunk.store if (store) { - const notEnough = this.scene.chunkNow.checkIfThereAreNotEnoughTrees() + const notEnough = this.game.chunkService.chunk.checkIfThereAreNotEnoughTrees() if (notEnough) { return this.generateSecondSideQuest() } @@ -181,7 +176,7 @@ export class QuestService implements GameSceneService { } } - const sideQuests = this.scene.eventService.events.filter( + const sideQuests = this.game.eventService.events.filter( (e) => e.type === 'SIDE_QUEST_STARTED', ) if (sideQuests.length >= 1) { @@ -189,10 +184,10 @@ export class QuestService implements GameSceneService { } const taskUpdateFunc1: IGameQuestTaskFunc = () => { - if (this.scene.chunkNow instanceof Village) { - const warehouse = this.scene.chunkNow.getWarehouse() + if (this.game.chunkService.chunk instanceof VillageChunk) { + const warehouse = this.game.chunkService.chunk.warehouse if (warehouse) { - const wood = warehouse.getItemByType('WOOD') + const wood = warehouse.inventory.items.find((item) => item.type === 'WOOD') if (wood?.amount) { if (wood.amount >= 25) { return { @@ -214,11 +209,11 @@ export class QuestService implements GameSceneService { } } - const taskAction1 = new DonateWoodToVillageAction({ scene: this.scene }) + const taskAction1 = new DonateWoodToVillageAction({ game: this.game }) const taskUpdateFunc2: IGameQuestTaskFunc = () => { - if (this.scene.chunkNow instanceof Village) { - const store = this.scene.chunkNow.getStore() + if (this.game.chunkService.chunk instanceof VillageChunk) { + const store = this.game.chunkService.chunk.store if (store) { return { status: 'SUCCESS', @@ -240,7 +235,7 @@ export class QuestService implements GameSceneService { taskAction1, }) - this.scene.eventService.init({ + this.game.eventService.initEvent({ type: 'SIDE_QUEST_STARTED', title: quest.title, description: quest.description, diff --git a/src/lib/game/services/interface.ts b/src/lib/game/services/interface.ts new file mode 100644 index 00000000..6d992e78 --- /dev/null +++ b/src/lib/game/services/interface.ts @@ -0,0 +1,79 @@ +import type { + Game, + GameObject, + GameObjectFlag, + GameSceneType, + IGamePoll, + IGameQuest, + ITradeOffer, + WebSocketMessage, +} from '$lib/game/types' +import type { GameChunk } from '$lib/game/services/chunk/interface' + +export interface GameService { + game: Game + update: () => void +} + +export interface GameWagonService extends GameService { + wagon: Wagon + randomOutFlag: GameObjectFlag + randomNearFlag: GameObjectFlag + initWagon: (point: { x: number, y: number }) => void +} + +export interface Wagon extends GameObject { + fuel: number + visibilityArea: { + startX: number + endX: number + startY: number + endY: number + } + cargoType: 'CHEST' | undefined +} + +export interface GameActionService extends GameService { + getAmountFromChatCommand: (text: string) => number | null +} + +export interface GameRouteService extends GameService { + route: IGameRoute | undefined + nextFlag: GameObjectFlag | undefined + addChunk: (chunk: GameChunk) => void +} + +export interface IGameRoute { + startPoint: { x: number, y: number } + endPoint: { x: number, y: number } + chunks: GameChunk[] + addGlobalFlag: (point: { x: number, y: number }) => void + setEndPoint: (point: { x: number, y: number }) => void + removeFlag: (id: string) => void +} + +export interface GameEventService extends GameService { + events: IGameEvent[] + initEvent: (event: { + title: IGameEvent['title'] + description: IGameEvent['description'] + type: IGameEvent['type'] + secondsToEnd: number + scene?: GameSceneType + poll?: IGameEvent['poll'] + quest?: IGameEvent['quest'] + offers?: IGameEvent['offers'] + }) => IGameEvent +} + +export interface IGameEvent { + id: string + title: string + description: string + type: WebSocketMessage['event'] + status: 'STARTED' | 'STOPPED' + endsAt: Date + poll?: IGamePoll + quest?: IGameQuest + offers?: ITradeOffer[] +} diff --git a/src/lib/game/services/player/interface.ts b/src/lib/game/services/player/interface.ts new file mode 100644 index 00000000..4bf5b5f9 --- /dev/null +++ b/src/lib/game/services/player/interface.ts @@ -0,0 +1,6 @@ +import type { GameService } from '$lib/game/services/interface' +import type { GameObjectPlayer } from '$lib/game/types' + +export interface GamePlayerService extends GameService { + findOrCreatePlayer: (id: string) => Promise +} diff --git a/src/lib/game/services/player/playerService.ts b/src/lib/game/services/player/playerService.ts new file mode 100644 index 00000000..bae7a913 --- /dev/null +++ b/src/lib/game/services/player/playerService.ts @@ -0,0 +1,76 @@ +import type { Game } from '$lib/game/types' +import { Player } from '$lib/game/objects/units/player' +import type { GamePlayerService } from '$lib/game/services/player/interface' +import { getDateMinusMinutes } from '$lib/date' +import { + MoveOffScreenAndSelfDestroyScript, +} from '$lib/game/scripts/moveOffScreenAndSelfDestroyScript' + +export class PlayerService implements GamePlayerService { + game: Game + + constructor(game: Game) { + this.game = game + } + + update() { + this.#removeInactivePlayers() + } + + async findOrCreatePlayer(id: string): Promise { + const player = this.#findPlayer(id) + if (!player && this.game.actionService.isActionPossible('CREATE_NEW_PLAYER')) { + return this.#createPlayer(id) + } + return player + } + + #findPlayer(id: string) { + return this.game.children.find((p) => p.id === id && p.type === 'PLAYER') as Player | undefined + } + + async #initPlayer(id: string) { + const instance = new Player({ game: this.game, id, x: -100, y: -100 }) + await instance.initFromDB() + await instance.initInventoryFromDB() + + const flag = this.game.wagonService.randomOutFlag + instance.x = flag.x + instance.y = flag.y + + instance.init() + + return instance + } + + async #createPlayer(id: string): Promise { + const player = this.#findPlayer(id) + if (!player) { + return this.#initPlayer(id) + } + return player + } + + #removeInactivePlayers() { + const players = this.game.activePlayers + for (const player of players) { + const checkTime = getDateMinusMinutes(8) + if (player.lastActionAt.getTime() <= checkTime.getTime()) { + if (player.script) { + continue + } + + const target = this.game.wagonService.randomOutFlag + const selfDestroyFunc = () => { + this.game.group.remove(player) + } + + player.script = new MoveOffScreenAndSelfDestroyScript({ + target, + object: player, + selfDestroyFunc, + }) + } + } + } +} diff --git a/src/lib/game/services/routeService.ts b/src/lib/game/services/routeService.ts new file mode 100644 index 00000000..e7ac5997 --- /dev/null +++ b/src/lib/game/services/routeService.ts @@ -0,0 +1,98 @@ +import type { + Game, +} from '$lib/game/types' +import { Route } from '$lib/game/common/route' +import type { + GameRouteService, + IGameRoute, +} from '$lib/game/services/interface' +import type { + GameChunk, + IGameVillageChunk, +} from '$lib/game/services/chunk/interface' +import { TreeObject } from '$lib/game/objects/treeObject' +import { StoneObject } from '$lib/game/objects/stoneObject' +import type { FlagObject } from '$lib/game/objects/flagObject' + +export class RouteService implements GameRouteService { + route: Route | undefined + game: Game + + constructor(game: Game) { + this.game = game + } + + update() { + if (!this.route?.flags || this.route.flags.length <= 0) { + if ( + this.game.eventService.events.find( + (e) => e.type === 'MAIN_QUEST_STARTED', + ) + ) { + return this.finishAdventure() + } + } + + if (this.route) { + for (const flag of this.route.flags) { + void flag.live() + } + } + } + + public getRoute(): IGameRoute | null { + if (!this.route) { + return null + } + + return this.route + } + + get nextFlag(): FlagObject | undefined { + return this.route?.flags[0] + } + + addChunk(chunk: GameChunk) { + this.route?.chunks.push(chunk) + } + + generateAdventure(village: IGameVillageChunk, chunks: number) { + const wagonStartPoint = village.wagonStop + const villageOutPoint = village.randomOutPoint + if (!wagonStartPoint) { + return + } + + this.route = new Route({ game: this.game }) + this.route.addGlobalFlag({ x: wagonStartPoint.x, y: wagonStartPoint.y }) + this.route.startPoint = { x: wagonStartPoint.x, y: wagonStartPoint.y } + this.addChunk(village) + + this.game.chunkService.generateChunks({ x: villageOutPoint.x, y: villageOutPoint.y }, chunks) + this.#markObjectsAsOnWagonPath(this.route) + } + + #markObjectsAsOnWagonPath(route: Route) { + for (const object of this.game.children) { + if (object instanceof TreeObject || object instanceof StoneObject) { + const isOnPath = route.checkIfPointIsOnWagonPath({ + x: object.x, + y: object.y, + }) + if (isOnPath) { + object.isOnWagonPath = true + } + } + } + } + + finishAdventure() { + console.log('Adventure finished!', new Date()) + this.route = undefined + this.game.wagonService.wagon.emptyCargo() + this.game.tradeService.traderIsMovingWithWagon = false + this.game.tradeService.handleTradeIsOver() + + this.game.chunkService.removeAllOutsideChunks() + } +} diff --git a/src/lib/game/scenes/services/tradeService.ts b/src/lib/game/services/tradeService.ts similarity index 60% rename from src/lib/game/scenes/services/tradeService.ts rename to src/lib/game/services/tradeService.ts index 53afdf95..073362dc 100644 --- a/src/lib/game/scenes/services/tradeService.ts +++ b/src/lib/game/services/tradeService.ts @@ -1,51 +1,43 @@ -import { Village } from '../../chunks' -import { Poll } from '../../common' -import { Flag } from '../../objects' -import type { Player } from '../../objects/units' -import { Trader } from '../../objects/units' -import { MoveOffScreenAndSelfDestroyScript } from '../../scripts/moveOffScreenAndSelfDestroyScript' -import { MoveToTargetScript } from '../../scripts/moveToTargetScript' -import { MoveToTradePostAndTradeScript } from '../../scripts/moveToTradePostAndTradeScript' +import { MoveOffScreenAndSelfDestroyScript } from '../scripts/moveOffScreenAndSelfDestroyScript' +import { MoveToTargetScript } from '../scripts/moveToTargetScript' +import { MoveToTradePostAndTradeScript } from '../scripts/moveToTradePostAndTradeScript' import { getRandomInRange } from '$lib/random' -import type { GameScene, GameSceneService, ITradeOffer } from '$lib/game/types' - -interface ITradeServiceOptions { - scene: GameScene -} - -export class TradeService implements GameSceneService { +import type { + Game, + GameObjectPlayer, ITradeOffer, +} from '$lib/game/types' +import { VillageChunk } from '$lib/game/services/chunk/villageChunk' +import { FlagObject } from '$lib/game/objects/flagObject' +import { Trader } from '$lib/game/objects/units/trader' +import { Poll } from '$lib/game/common/poll' +import type { GameService } from '$lib/game/services/interface' + +export class TradeService implements GameService { public offers: ITradeOffer[] = [] public tradeWasSuccessful: boolean public traderIsMovingWithWagon: boolean - public scene: GameScene + game: Game - constructor({ scene }: ITradeServiceOptions) { - this.scene = scene + constructor(game: Game) { + this.game = game this.traderIsMovingWithWagon = false this.tradeWasSuccessful = false } public update() { - this.checkAndGenerateTrader() - this.checkClosedOffers() + this.#checkAndGenerateTrader() + this.#checkClosedOffers() + this.#updateTrader() } - public getTrader() { - return this.scene.objects.find((obj) => obj instanceof Trader) as - | Trader - | undefined - } - - public getStore() { - if (this.scene.chunkNow instanceof Village) { - return this.scene.chunkNow.getStore() - } + getTrader() { + return this.game.children.find((obj) => obj.type === 'TRADER') } - public async findActiveOfferAndTrade( + async findActiveOfferAndTrade( offerId: string, amount: number, - player: Player, + player: GameObjectPlayer, ) { for (const offer of this.offers) { if (offer.id === offerId) { @@ -60,10 +52,10 @@ export class TradeService implements GameSceneService { return 'OFFER_NOT_FOUND' } - public async trade( + async trade( offer: ITradeOffer, amount: number, - player: Player, + player: GameObjectPlayer, ): Promise { if (offer.amount < amount) { return false @@ -85,7 +77,12 @@ export class TradeService implements GameSceneService { return false } - public updateTrader(object: Trader) { + #updateTrader() { + const object = this.getTrader() + if (!object) { + return + } + object.live() if (!object.script) { @@ -93,7 +90,7 @@ export class TradeService implements GameSceneService { // Moving near Wagon const random = getRandomInRange(1, 150) if (random <= 1) { - const target = this.scene.wagonService.findRandomNearFlag() + const target = this.game.wagonService.randomNearFlag object.script = new MoveToTargetScript({ object, target, @@ -103,12 +100,12 @@ export class TradeService implements GameSceneService { } // Moving to Trade - if (this.checkIfNeedToStartTrade()) { - if (this.scene.chunkNow instanceof Village) { - const target = this.getTradePointFlag() + if (this.#isNeedToStartTrade()) { + if (this.game.chunkService.chunk instanceof VillageChunk) { + const target = this.#getTradePointFlag() if (target) { const startTradeFunc = () => { - this.scene.eventService.init({ + this.game.eventService.initEvent({ title: 'Trade in progress', description: '', type: 'TRADE_STARTED', @@ -128,15 +125,15 @@ export class TradeService implements GameSceneService { } } - public handleTradeIsOver() { + handleTradeIsOver() { const trader = this.getTrader() if (!trader) { return } - const target = this.scene.wagonService.findRandomOutFlag() + const target = this.game.wagonService.randomOutFlag const selfDestroyFunc = () => { - this.scene.removeObject(trader) + this.game.removeObject(trader) } trader.script = new MoveOffScreenAndSelfDestroyScript({ @@ -150,62 +147,61 @@ export class TradeService implements GameSceneService { public removeTrade() { this.offers = [] - for (const event of this.scene.eventService.events) { + for (const event of this.game.eventService.events) { if (event.type === 'TRADE_STARTED') { - this.scene.eventService.destroy(event) + this.game.eventService.destroy(event) } } } - private checkIfNeedToStartTrade(): boolean { + #isNeedToStartTrade(): boolean { if (this.tradeWasSuccessful) { return false } - const activeTrade = this.scene.eventService.events.find( + const activeTrade = this.game.eventService.events.find( (e) => e.type === 'TRADE_STARTED', ) return !activeTrade } - private getTradePointFlag() { - return this.scene.chunkNow?.objects.find( - (obj) => obj instanceof Flag && obj.type === 'TRADE_POINT', - ) as Flag | undefined + #getTradePointFlag() { + return this.game.children.find( + (obj) => obj instanceof FlagObject && obj.variant === 'TRADE_POINT', + ) as FlagObject | undefined } - private createTradeTargetFlagNearStore() { - if (this.getTradePointFlag()) { + #createTradeTargetFlagNearStore() { + if (this.#getTradePointFlag()) { return } - const store = this.getStore() + const store = this.game.chunkService.chunk?.store if (!store) { return } - const flag = new Flag({ - scene: this.scene, + new FlagObject({ + game: this.game, x: store.x + 105, y: store.y + 10, - type: 'TRADE_POINT', - }) - this.scene.chunkNow?.objects.push(flag) + variant: 'TRADE_POINT', + }).init() } - private checkAndGenerateTrader() { - if (this.scene.chunkNow instanceof Village) { - const store = this.scene.chunkNow.getStore() + #checkAndGenerateTrader() { + if (this.game.chunkService.chunk instanceof VillageChunk) { + const store = this.game.chunkService.chunk.store const trader = this.getTrader() if (store?.id && !trader?.id) { - this.createTradeTargetFlagNearStore() + this.#createTradeTargetFlagNearStore() this.generateNewTrader() this.generateTradeOffers() } } } - private checkClosedOffers() { + #checkClosedOffers() { for (const offer of this.offers) { if (offer.amount === 0) { this.tradeWasSuccessful = true @@ -216,10 +212,8 @@ export class TradeService implements GameSceneService { } private generateNewTrader() { - const { x, y } = this.scene.wagonService.findRandomOutFlag() - const trader = new Trader({ scene: this.scene, x, y }) - - this.scene.objects.push(trader) + const { x, y } = this.game.wagonService.randomOutFlag + new Trader({ game: this.game, x, y }).init() } private generateTradeOffers() { @@ -263,19 +257,19 @@ export class TradeService implements GameSceneService { return } - const store = this.getStore() + const store = this.game.chunkService.chunk?.store if (!store) { return } - const votingEvents = this.scene.eventService.events.filter( + const votingEvents = this.game.eventService.events.filter( (e) => e.type === 'VOTING_FOR_NEW_MAIN_QUEST_STARTED', ) if (votingEvents.length >= 1) { return } - const adventureEvents = this.scene.eventService.events.filter( + const adventureEvents = this.game.eventService.events.filter( (e) => e.type === 'MAIN_QUEST_STARTED', ) if (adventureEvents.length >= 1) { @@ -283,18 +277,18 @@ export class TradeService implements GameSceneService { } const votesToSuccess - = this.scene.findActivePlayers().length >= 2 - ? this.scene.findActivePlayers().length + = this.game.activePlayers.length >= 2 + ? this.game.activePlayers.length : 1 - const poll = new Poll({ votesToSuccess, scene: this.scene }) + const poll = new Poll({ votesToSuccess, game: this.game }) - this.scene.eventService.init({ + this.game.eventService.initEvent({ type: 'VOTING_FOR_NEW_MAIN_QUEST_STARTED', title: 'The merchant offers a quest', description: 'Let\'s make the quest active? Vote in chat.', secondsToEnd: 180, - quest: this.scene.eventService.questService.create({ + quest: this.game.eventService.questService.create({ status: 'INACTIVE', type: 'MAIN', title: 'Transport cargo to a neighboring village', diff --git a/src/lib/game/scenes/services/wagonService.ts b/src/lib/game/services/wagonService.ts similarity index 57% rename from src/lib/game/scenes/services/wagonService.ts rename to src/lib/game/services/wagonService.ts index e58f0f4a..4fbf42ac 100644 --- a/src/lib/game/scenes/services/wagonService.ts +++ b/src/lib/game/services/wagonService.ts @@ -1,44 +1,45 @@ -import { Flag, Wagon } from '../../objects' import { getMinusOrPlus, getRandomInRange } from '$lib/random' -import type { GameScene, GameSceneService } from '$lib/game/types' - -interface IWagonServiceOptions { - scene: GameScene -} - -export class WagonService implements GameSceneService { - wagon!: Wagon - outFlags: Flag[] = [] - nearFlags: Flag[] = [] - scene: GameScene - - constructor({ scene }: IWagonServiceOptions) { - this.scene = scene +import type { Game } from '$lib/game/types' +import { FlagObject } from '$lib/game/objects/flagObject' +import { BaseWagon } from '$lib/game/objects/baseWagon' +import type { + GameWagonService, +} from '$lib/game/services/interface' + +export class WagonService implements GameWagonService { + wagon!: BaseWagon + game: Game + + #outFlags: FlagObject[] = [] + #nearFlags: FlagObject[] = [] + + constructor(game: Game) { + this.game = game } update() { - this.updateWagon() - this.updateFlags() + this.#updateWagon() + this.#updateFlags() } - public initWagon({ x, y }: { x: number, y: number }) { - this.wagon = new Wagon({ scene: this.scene, x, y }) + initWagon({ x, y }: { x: number, y: number }) { + this.wagon = new BaseWagon({ game: this.game, x, y }) - this.initOutFlags() - this.initNearFlags() + this.#initOutFlags() + this.#initNearFlags() } - public findRandomOutFlag() { - return this.outFlags[Math.floor(Math.random() * this.outFlags.length)] + get randomOutFlag() { + return this.#outFlags[Math.floor(Math.random() * this.#outFlags.length)] } - public findRandomNearFlag() { - return this.nearFlags[Math.floor(Math.random() * this.nearFlags.length)] + get randomNearFlag() { + return this.#nearFlags[Math.floor(Math.random() * this.#nearFlags.length)] } - private updateWagon() { + #updateWagon() { const collisionObjects - = this.scene.chunkNow?.objects.filter( + = this.game.children.filter( (obj) => obj.isOnWagonPath && obj.state !== 'DESTROYED', ) ?? [] for (const collisionObject of collisionObjects) { @@ -63,7 +64,7 @@ export class WagonService implements GameSceneService { this.wagon.state = 'IDLE' } if (this.wagon.state === 'IDLE') { - const target = this.scene.routeService.route?.getNextFlag() + const target = this.game.routeService.nextFlag if (target) { this.wagon.target = target this.wagon.state = 'MOVING' @@ -74,11 +75,8 @@ export class WagonService implements GameSceneService { const isMoving = this.wagon.move() if (!isMoving) { - if ( - this.wagon.target instanceof Flag - && this.wagon.target.type === 'WAGON_MOVEMENT' - ) { - this.scene.routeService.route?.removeFlag(this.wagon.target) + if (this.wagon.target?.type === 'FLAG') { + this.game.routeService.route?.removeFlag(this.wagon.target.id) this.wagon.target = undefined this.wagon.state = 'IDLE' this.wagon.speedPerSecond = 0 @@ -89,30 +87,30 @@ export class WagonService implements GameSceneService { this.wagon.live() } - private updateFlags() { - for (const flag of this.nearFlags) { + #updateFlags() { + for (const flag of this.#nearFlags) { flag.x = this.wagon.x + flag.offsetX flag.y = this.wagon.y + flag.offsetY } - for (const flag of this.outFlags) { + for (const flag of this.#outFlags) { flag.x = this.wagon.x + flag.offsetX flag.y = this.wagon.y + flag.offsetY } } - private initOutFlags(count = 20) { + #initOutFlags(count = 20) { for (let i = 0; i < count; i++) { - this.outFlags.push(this.generateRandomOutFlag()) + this.#outFlags.push(this.#generateRandomOutFlag()) } } - private initNearFlags(count = 20) { + #initNearFlags(count = 20) { for (let i = 0; i < count; i++) { - this.nearFlags.push(this.generateRandomNearFlag()) + this.#nearFlags.push(this.#generateRandomNearFlag()) } } - private generateRandomOutFlag() { + #generateRandomOutFlag() { const minOffsetX = 1800 const minOffsetY = 1200 @@ -121,9 +119,9 @@ export class WagonService implements GameSceneService { const offsetY = getRandomInRange(minOffsetY, minOffsetY * 1.5) * getMinusOrPlus() - return new Flag({ - scene: this.scene, - type: 'OUT_OF_SCREEN', + return new FlagObject({ + game: this.game, + variant: 'OUT_OF_SCREEN', x: this.wagon.x + offsetX, y: this.wagon.y + offsetY, offsetX, @@ -131,7 +129,7 @@ export class WagonService implements GameSceneService { }) } - private generateRandomNearFlag() { + #generateRandomNearFlag() { const minRadius = 280 const maxRadius = minRadius * 1.1 @@ -141,9 +139,9 @@ export class WagonService implements GameSceneService { const offsetX = Math.round(Math.cos(angle) * radius) const offsetY = Math.round(Math.sin(angle) * radius) - return new Flag({ - scene: this.scene, - type: 'WAGON_NEAR_MOVEMENT', + return new FlagObject({ + game: this.game, + variant: 'WAGON_NEAR_MOVEMENT', x: this.wagon.x + offsetX, y: this.wagon.y + offsetY, offsetX, diff --git a/src/lib/game/types.ts b/src/lib/game/types.ts index 559428b7..d6e9d7dd 100644 --- a/src/lib/game/types.ts +++ b/src/lib/game/types.ts @@ -1,21 +1,70 @@ -export interface Game { - +import type { Container, TilingSprite } from 'pixi.js' +import type { GameAction } from '$lib/game/actions/interface' +import type { + GameActionService, + GameEventService, + GameRouteService, + GameService, + GameWagonService, + IGameEvent, + IGameRoute, Wagon, +} from '$lib/game/services/interface' +import type { + GameChunk, + GameChunkService, IGameChunkTheme, +} from '$lib/game/services/chunk/interface' +import type { GamePlayerService } from '$lib/game/services/player/interface' + +export interface Game extends Container { + children: GameObject[] + tick: number + audio: GameAudio + scene: GameScene + bg: GameBackground + group: IGameGroup + activePlayers: GameObjectPlayer[] + actionService: GameActionService + eventService: GameEventService + tradeService: GameService + wagonService: GameWagonService + routeService: GameRouteService + chunkService: GameChunkService + playerService: GamePlayerService + play: () => void + checkIfThisFlagIsTarget: (id: string) => boolean + initScene: (scene: GameSceneType) => void + removeObject: (obj: GameObject) => void + stopRaid: () => void + initRaiders: (count: number) => void } export interface GameScene { game: Game - live: () => void + destroy: () => void } -export interface GameSceneService { - scene: GameScene - update: () => void +export interface GameBackground { + generateBackgroundTilingSprite: (theme: IGameChunkTheme) => TilingSprite } -export interface GameObject { +export interface GameAudio { + playSound: (name: GameAudioName) => void + isEnabled: boolean + destroy: () => void +} + +export type GameAudioName = + | 'CHOP_HIT' + | 'MINE_HIT' + | 'HAND_HIT' + | 'MARCHING_WITH_HORNS' + | 'FOREST_BACKGROUND' + | 'WAGON_MOVING' + | 'FIRE_BURN' + | 'YEAH' + +export interface GameObject extends Container { id: string - x: number - y: number type: GameObjectType state: IGameObjectState direction: IGameObjectDirection @@ -23,11 +72,15 @@ export interface GameObject { health: number speedPerSecond: number size: number - scene: GameScene + chunkId: string | undefined + isOnWagonPath: boolean + game: Game script: IGameScript | undefined + init: () => void live: () => void animate: () => void move: () => boolean + setTarget: (obj: GameObject) => void } type GameObjectType = @@ -64,15 +117,6 @@ export interface TwitchAccessToken { obtainmentTimestamp: number } -export interface IGameAction { - command: string - commandDescription: string - live: ( - player: IGameObjectPlayer, - params: string[], - ) => Promise -} - export interface IGameActionResponse { ok: boolean message: string | null @@ -107,6 +151,8 @@ export interface IGameInventory { id: string objectId: string items: IGameInventoryItem[] + reduceOrDestroyItem: (type: ItemType, amount: number) => Promise + addOrCreateItem: (type: ItemType, amount: number) => Promise } export interface IGameInventoryItem { @@ -151,38 +197,13 @@ export interface IGameQuestTask { progressToSuccess: number | boolean updateProgress: IGameQuestTaskFunc command?: string - action?: IGameAction + action?: GameAction } export type IGameQuestTaskFunc = ( progressToSuccess?: IGameQuestTask['progressToSuccess'], ) => Partial -export interface IGameChunk { - id: string - title: string - type: 'VILLAGE' | 'FOREST' | 'LAKE' - center: { - x: number - y: number - } - area: IGameObjectArea -} - -export type IGameChunkTheme = - | 'GREEN' - | 'TOXIC' - | 'STONE' - | 'TEAL' - | 'BLUE' - | 'VIOLET' - -export interface IGameVillageChunk extends IGameChunk {} - -export interface IGameForestChunk extends IGameChunk {} - -export interface IGameLakeChunk extends IGameChunk {} - export type IGameObjectState = | 'MOVING' | 'IDLE' @@ -209,17 +230,6 @@ export interface WebSocketMessage { object?: Partial } -export interface IGameObjectWagon extends GameObject { - fuel: number - visibilityArea: { - startX: number - endX: number - startY: number - endY: number - } - cargoType: 'CHEST' | undefined -} - export type GameObjectBuildingType = | 'CAMPFIRE' | 'WAREHOUSE' @@ -298,6 +308,7 @@ export interface IGameObjectUnit extends GameObject { dialogue: { messages: { id: string, text: string }[] } + chopTree: () => void } export interface IGameObjectTrader extends IGameObjectUnit {} @@ -308,13 +319,14 @@ export interface IGameObjectFarmer extends IGameObjectUnit {} export interface IGameObjectMechanic extends IGameObjectUnit {} -export interface IGameObjectPlayer extends IGameObjectUnit { +export interface GameObjectPlayer extends IGameObjectUnit { reputation: number villainPoints: number refuellerPoints: number raiderPoints: number skills: IGameSkill[] lastActionAt: Date + addReputation: (amount: number) => void } export interface IGameObjectRaider extends IGameObjectUnit {} @@ -346,48 +358,30 @@ export interface IGameTask { live: () => void } -export interface IGameEvent { - id: string - title: string - description: string - type: WebSocketMessage['event'] - status: 'STARTED' | 'STOPPED' - endsAt: Date - poll?: IGamePoll - quest?: IGameQuest - offers?: ITradeOffer[] -} - export interface IGamePoll { status: 'ACTIVE' | 'SUCCESS' | 'FINISHED' id: string - action: IGameAction + action: GameAction votesToSuccess: number votes: { id: string, userName: string }[] } export type GameSceneType = 'VILLAGE' | 'DEFENCE' | 'MOVING' -export interface GetSceneResponse { +export interface GameStateResponse { id: string commands: string[] - chunk: IGameChunk | null + chunk: GameChunk | undefined events: IGameEvent[] group: IGameGroup - wagon: IGameObjectWagon + wagon: Wagon route: IGameRoute | null warehouseItems: IGameInventoryItem[] | undefined } export interface IGameGroup { id: string - players: IGameObjectPlayer[] -} - -export interface IGameRoute { - startPoint: { x: number, y: number } - endPoint: { x: number, y: number } - chunks: IGameChunk[] + players: GameObjectPlayer[] } export interface PlayerTitle { @@ -422,7 +416,7 @@ export type GraphicsContainerType = | 'FIRE_PARTICLE' interface PlayerWithPoints { - player: IGameObjectPlayer + player: GameObjectPlayer points: number } diff --git a/src/lib/game/utils/audioManager.ts b/src/lib/game/utils/audioManager.ts index f4068177..87707419 100644 --- a/src/lib/game/utils/audioManager.ts +++ b/src/lib/game/utils/audioManager.ts @@ -7,87 +7,87 @@ import marchWithHorns1Audio from '$lib/assets/game/audio/marching-with-horns-1.w import mine1Audio from '$lib/assets/game/audio/mine-1.wav' import wagon1Audio from '$lib/assets/game/audio/wagon-1.wav' import yeah1Audio from '$lib/assets/game/audio/yeah-1.wav' +import type { GameAudio, GameAudioName } from '$lib/game/types' -type SoundName = - | 'CHOP_HIT' - | 'MINE_HIT' - | 'HAND_HIT' - | 'MARCHING_WITH_HORNS' - | 'FOREST_BACKGROUND' - | 'WAGON_MOVING' - | 'FIRE_BURN' - | 'YEAH' - -export class AudioManager { +export class AudioManager implements GameAudio { public isEnabled = false - private chop1 = new Howl({ + #chop1 = new Howl({ src: chop1Audio, }) - private mine1 = new Howl({ + #mine1 = new Howl({ src: mine1Audio, rate: 0.7, volume: 0.4, }) - private handPunch1 = new Howl({ + #handPunch1 = new Howl({ src: handPunch1Audio, volume: 0.2, rate: 0.8, }) - private marchWithHorns1 = new Howl({ + #marchWithHorns1 = new Howl({ src: marchWithHorns1Audio, volume: 0.7, }) - private forest1 = new Howl({ + #forest1 = new Howl({ src: forest1Audio, loop: true, volume: 0.5, }) - private wagon1 = new Howl({ + #wagon1 = new Howl({ src: wagon1Audio, rate: 0.7, volume: 0.08, }) - private fireBurn1 = new Howl({ + #fireBurn1 = new Howl({ src: fireBurn1Audio, volume: 0.7, }) - private yeah1 = new Howl({ + #yeah1 = new Howl({ src: yeah1Audio, volume: 0.8, }) - private findSound(name: SoundName): Howl[] { + playSound(name: GameAudioName) { + return this.#play(this.#findSound(name)) + } + + destroy() { + this.#fireBurn1.stop() + this.#forest1.stop() + } + + #findSound(name: GameAudioName): Howl[] { switch (name) { case 'CHOP_HIT': - return [this.chop1] + return [this.#chop1] case 'MINE_HIT': - return [this.mine1] + return [this.#mine1] case 'HAND_HIT': - return [this.handPunch1] + return [this.#handPunch1] case 'MARCHING_WITH_HORNS': - return [this.marchWithHorns1] + return [this.#marchWithHorns1] case 'WAGON_MOVING': - return [this.wagon1] + return [this.#wagon1] case 'FIRE_BURN': - return [this.fireBurn1] + return [this.#fireBurn1] case 'YEAH': - return [this.yeah1] + return [this.#yeah1] case 'FOREST_BACKGROUND': - return [this.forest1] + return [this.#forest1] default: return [] } } - private play(audios: Howl[]) { + #play(audios: Howl[]) { if (!audios.length || !this.isEnabled) { return } @@ -99,13 +99,4 @@ export class AudioManager { randomAudio.play() } - - public playSound(name: SoundName) { - return this.play(this.findSound(name)) - } - - public destroy() { - this.fireBurn1.stop() - this.forest1.stop() - } } diff --git a/src/lib/game/utils/generators/background.ts b/src/lib/game/utils/generators/background.ts index a760a3e5..1446f23d 100644 --- a/src/lib/game/utils/generators/background.ts +++ b/src/lib/game/utils/generators/background.ts @@ -6,8 +6,8 @@ import { BACKGROUND_TILE_4, BACKGROUND_TILE_5, } from './backgroundImages' -import type { IGameChunkTheme } from '$lib/game/types' import { getRandomInRange } from '$lib/random' +import type { IGameChunkTheme } from '$lib/game/services/chunk/interface' interface Palette { 93: string @@ -78,7 +78,7 @@ export class BackgroundGenerator { }) } - public changePaletteByTheme(theme: IGameChunkTheme) { + changePaletteByTheme(theme: IGameChunkTheme) { if (theme === 'GREEN') { this.mainColor1 = '0x239063' this.mainColor2 = '0x1ebc73' @@ -209,7 +209,9 @@ export class BackgroundGenerator { return [red, red + 1, red + 2, red + 3] } - public getGeneratedBackgroundTilingSprite() { + generateBackgroundTilingSprite(theme: IGameChunkTheme) { + this.changePaletteByTheme(theme) + const bg = this.generateRandomGridBackground({ width: 2000, height: 1000, diff --git a/src/routes/(game)/play/GameChunkInfo.svelte b/src/routes/(game)/play/GameChunkInfo.svelte index 0a672a19..86362680 100644 --- a/src/routes/(game)/play/GameChunkInfo.svelte +++ b/src/routes/(game)/play/GameChunkInfo.svelte @@ -2,9 +2,9 @@ import Home from 'lucide-svelte/icons/home' import TreeDeciduous from 'lucide-svelte/icons/tree-deciduous' import Waves from 'lucide-svelte/icons/waves' - import type { IGameChunk } from '$lib/game/types' + import type { GameChunk } from '$lib/game/services/chunk/interface' - export let type: IGameChunk['type'] + export let type: GameChunk['type'] export let title diff --git a/src/routes/(game)/play/GameEventCard.svelte b/src/routes/(game)/play/GameEventCard.svelte index f2949338..3a37555e 100644 --- a/src/routes/(game)/play/GameEventCard.svelte +++ b/src/routes/(game)/play/GameEventCard.svelte @@ -4,7 +4,7 @@ import GameQuestConditions from './GameQuestConditions.svelte' import GameQuestTask from './GameQuestTask.svelte' import GameTradeOffer from './GameTradeOffer.svelte' - import type { IGameEvent } from '$lib/game/types' + import type { IGameEvent } from '$lib/game/services/interface' export let event: IGameEvent const isActive = event.status === 'STARTED' diff --git a/src/routes/(game)/play/GameEvents.svelte b/src/routes/(game)/play/GameEvents.svelte index 38dfc6a9..aa7d2609 100644 --- a/src/routes/(game)/play/GameEvents.svelte +++ b/src/routes/(game)/play/GameEvents.svelte @@ -1,6 +1,6 @@ diff --git a/src/routes/(game)/play/GamePlayerCard.svelte b/src/routes/(game)/play/GamePlayerCard.svelte index d902b9d9..40fbb256 100644 --- a/src/routes/(game)/play/GamePlayerCard.svelte +++ b/src/routes/(game)/play/GamePlayerCard.svelte @@ -1,9 +1,9 @@
diff --git a/src/routes/(game)/play/GameRouteInfo.svelte b/src/routes/(game)/play/GameRouteInfo.svelte index 6c58d802..88b8e907 100644 --- a/src/routes/(game)/play/GameRouteInfo.svelte +++ b/src/routes/(game)/play/GameRouteInfo.svelte @@ -1,6 +1,6 @@