diff --git a/.dockerignore b/.dockerignore index 7ee74781..282d8ed0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,7 +15,7 @@ README.md build package docker-compose* -LICENSE.txt +LICENSE Makefile helm-charts .env diff --git a/.env.example b/.env.example index f0d13c1e..19630df9 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,5 @@ -TWITCH_CHANNEL_NAME="" -TWITCH_CHANNEL_ID="" - # Go to your Twitch developer console and create a new application PUBLIC_TWITCH_CLIENT_ID="" -TWITCH_SECRET_ID="" - -# Prepare URL and visit https://id.twitch.tv/oauth2/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&scope=chat:read+chat:edit+channel:read:redemptions+moderator:manage:announcements -# After you will get page with long URL, where will be "code" as query param -TWITCH_OAUTH_CODE="" # Will redirect to with #access_token PUBLIC_SIGNIN_REDIRECT_URL="" @@ -19,7 +11,7 @@ PUBLIC_COOKIE_KEY="" PRIVATE_JWT_SECRET_KEY="" # WebSocket server with event messages -PUBLIC_WEBSOCKET_URL="ws://localhost:4002" +PUBLIC_WEBSOCKET_URL="" # Example of local DB DATABASE_URL="postgresql://postgres:postgres@localhost:6432/db?schema=public&connect_timeout=300" \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/README.md b/README.md index d62b7d9a..0eaeeb29 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ Let's build a similar world together! ⭐️ Become a Stargazer ⭐️ - [Svelte](https://svelte.dev/): A new way to build web applications. It's a compiler that takes your declarative components and converts them into efficient JavaScript that surgically updates the DOM. - [SvelteKit](https://kit.svelte.dev/): A framework for rapidly developing robust, performant web applications using Svelte. - [Twurple](https://twurple.js.org/): A set of libraries that aims to cover all existing Twitch APIs. -- [Prisma](https://www.prisma.io/): Next-generation Node.js and TypeScript ORM. - [Howler.js](https://howlerjs.com/): Audio library for the modern web. - [Lucide Svelte](https://lucide.dev/guide/packages/lucide-svelte): An open-source icon library. - [TypeScript](https://www.typescriptlang.org/): A strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. @@ -41,4 +40,4 @@ npm run dev ## 🪀 License -This project is licensed under the MIT License - see the [**MIT License**](https://github.com/hmbanan666/chat-game/blob/main/LICENSE.txt) file for details. +This project is licensed under the MIT License - see the [**MIT License**](https://github.com/hmbanan666/chat-game/blob/main/LICENSE) file for details. diff --git a/apps/api/src/bot/bot.controller.ts b/apps/api/src/bot/bot.controller.ts deleted file mode 100644 index b701cd91..00000000 --- a/apps/api/src/bot/bot.controller.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { RefreshingAuthProvider } from '@twurple/auth' -import { Bot } from '@twurple/easy-bot' -import { PubSubClient } from '@twurple/pubsub' -import type { TwitchAccessTokenResponse } from '../../../../packages/api-sdk/src' -import { DONATE_URL } from '../config' -import { DBRepository } from '../db/db.repository' -import type { Game } from '../game/game' -import { BotService } from './bot.service' - -export class BotController { - private readonly channel = process.env.TWITCH_CHANNEL_NAME as string - private readonly userId = process.env.TWITCH_CHANNEL_ID as string - private readonly clientId = process.env.TWITCH_CLIENT_ID as string - private readonly clientSecret = process.env.TWITCH_SECRET_ID as string - private readonly code = process.env.TWITCH_OAUTH_CODE as string - private readonly redirectUrl = 'http://localhost:3000' - - private readonly service: BotService - private readonly repository: DBRepository - - constructor(game: Game) { - this.service = new BotService(game) - this.repository = new DBRepository() - } - - private async obtainTwitchAccessToken() { - try { - const response = await fetch( - `https://id.twitch.tv/oauth2/token?client_id=${this.clientId}&client_secret=${this.clientSecret}&code=${this.code}&grant_type=authorization_code&redirect_uri=${this.redirectUrl}`, - { - method: 'POST', - }, - ) - return (await response.json()) as TwitchAccessTokenResponse - } catch (err) { - console.error('obtainTwitchAccessToken', err) - return null - } - } - - private async createNewAccessToken(): Promise { - const res = await this.obtainTwitchAccessToken() - if (res?.access_token) { - await this.repository.createTwitchAccessToken({ - userId: this.userId, - accessToken: res.access_token, - refreshToken: res.refresh_token, - scope: res.scope, - expiresIn: res.expires_in, - obtainmentTimestamp: new Date().getTime(), - }) - - throw new Error('Saved new access token. Restart server!') - } - - throw new Error( - 'No access token found and no Twitch code. See .env.example', - ) - } - - private async prepareAuthProvider() { - const accessToken = await this.repository.getTwitchAccessToken(this.userId) - if (!accessToken) { - return this.createNewAccessToken() - } - - const authProvider = new RefreshingAuthProvider({ - clientId: this.clientId, - clientSecret: this.clientSecret, - }) - - authProvider.onRefresh(async (userId, newTokenData) => { - await this.repository.updateTwitchAccessToken(userId, newTokenData) - }) - - await authProvider.addUserForToken(accessToken, [ - 'chat', - 'channel', - 'moderator', - ]) - - return authProvider - } - - public async serve() { - const authProvider = await this.prepareAuthProvider() - - const pubSubClient = new PubSubClient({ authProvider }) - - pubSubClient.onRedemption( - this.userId, - ({ userId, userName, rewardId, message }) => { - this.service.handleChannelRewardRedemption({ - userId, - userName, - rewardId, - message, - }) - }, - ) - - const bot = new Bot({ - authProvider, - channels: [this.channel], - chatClientOptions: { - requestMembershipEvents: true, - }, - }) - - bot.onRaid(({ userName, userId, viewerCount }) => { - void this.service.handleRaid({ userName, userId, viewerCount }) - }) - - bot.onMessage(async (message) => { - const replyText = await this.service.handleMessage({ - userId: message.userId, - userName: message.userName, - text: message.text, - }) - if (replyText) { - await message.reply(replyText) - } - }) - - bot.onJoin(({ userName }) => { - console.log(new Date().toLocaleTimeString(), 'joined!', userName) - }) - - bot.onLeave(({ userName }) => { - console.log('left!', userName) - }) - - setInterval( - () => { - bot.announce( - this.channel, - `Basic commands: !chop, !mine, !help. Thanks to everyone who contributed for a cool website! :D ${DONATE_URL}`, - 'orange', - ) - }, - 1000 * 60 * 15, - ) - - return bot - } -} diff --git a/apps/api/src/bot/bot.service.ts b/apps/api/src/bot/bot.service.ts deleted file mode 100644 index 75e4fe85..00000000 --- a/apps/api/src/bot/bot.service.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { TWITCH_CHANNEL_REWARDS } from '../config' -import type { Game } from '../game/game' - -export class BotService { - public game: Game - - constructor(game: Game) { - this.game = game - } - - async handleMessage({ - userName, - userId, - text, - }: { - userName: string - userId: string - text: string - }) { - const strings = text.split(' ') - - const possibleCommand = strings[0].substring(1) - const otherStrings = strings.toSpliced(0, 1) - const firstChar = strings[0].charAt(0) - - if (firstChar === '!') { - const activeAction - = this.game.scene.actionService.findActionByCommand(possibleCommand) - if (activeAction) { - const action = activeAction.action - const params = otherStrings - - const result = await this.game.handleActionFromChat({ - action, - userId, - userName, - params, - }) - if (result.message) { - return result.message - } - - return - } - - const dynamicAction - = this.game.scene.actionService.findDynamicActionByCommand( - possibleCommand, - ) - if (dynamicAction) { - const params = otherStrings - - const result = await this.game.handleDynamicActionFromChat({ - action: dynamicAction, - userId, - userName, - params, - }) - if (result.message) { - return result.message - } - } - } - - void this.game.handleActionFromChat({ - action: 'SHOW_MESSAGE', - userId, - userName, - params: [text], - }) - } - - public async handleRaid({ - userName, - userId, - viewerCount, - }: { - userName: string - userId: string - viewerCount: number - }) { - return this.game.handleActionFromChat({ - action: 'START_RAID', - userId, - userName, - params: [viewerCount.toString()], - }) - } - - public async handleChannelRewardRedemption({ - userId, - userName, - rewardId, - message, - }: { - userId: string - userName: string - rewardId: string - message: string - }) { - console.log( - 'The viewer bought a reward using channel points', - userId, - userName, - rewardId, - message, - ) - const player = await this.game.repository.findOrCreatePlayer( - userId, - userName, - ) - if (rewardId === TWITCH_CHANNEL_REWARDS.add150ViewerPointsId) { - await this.game.repository.addPlayerViewerPoints(player.id, 150) - return - } - if (rewardId === TWITCH_CHANNEL_REWARDS.villainStealFuelId) { - return this.game.handleActionFromChat({ - action: 'STEAL_FUEL', - userId, - userName, - }) - } - if (rewardId === TWITCH_CHANNEL_REWARDS.addNewIdea) { - return this.game.handleActionFromChat({ - action: 'CREATE_IDEA', - userId, - userName, - params: [message], - }) - } - } -} diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts deleted file mode 100644 index f5cef338..00000000 --- a/apps/api/src/config.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const SERVER_TICK_MS = 25 - -export const MIN_X = 20 -export const MAX_X = 2520 - -export const MIN_Y = 120 -export const MAX_Y = 1190 - -export const DONATE_URL = 'https://www.donationalerts.com/r/hmbanan666' -export const DISCORD_SERVER_INVITE_URL = 'https://discord.gg/B6etUajrGZ' -export const GITHUB_REPO_URL = 'https://github.com/hmbanan666/chat-game' - -export const TWITCH_CHANNEL_REWARDS = { - add150ViewerPointsId: 'd8237822-c943-434f-9d7e-87a9f549f4c4', - villainStealFuelId: 'd5956de4-54ff-49e4-afbe-ee4e62718eee', - addNewIdea: '289457e8-18c2-4b68-8564-fc61dd60b2a2', -} - -export const ADMIN_PLAYER_ID = 'svhjz9p5467wne9ybasf1bwy' diff --git a/apps/api/src/db/db.client.ts b/apps/api/src/db/db.client.ts deleted file mode 100644 index 020681b0..00000000 --- a/apps/api/src/db/db.client.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '@prisma/client' - -export const db = new PrismaClient() diff --git a/apps/api/src/db/db.repository.ts b/apps/api/src/db/db.repository.ts deleted file mode 100644 index 631c2490..00000000 --- a/apps/api/src/db/db.repository.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { TwitchAccessToken } from '../../../../packages/api-sdk/src' -import { db } from './db.client' - -export class DBRepository { - private readonly db - - constructor() { - this.db = db - } - - async getTwitchAccessToken( - userId: string, - ): Promise { - const token = await db.twitchAccessToken.findUnique({ - where: { userId }, - }) - if (!token) { - return null - } - - return { - ...token, - obtainmentTimestamp: Number(token.obtainmentTimestamp), - } - } - - updateTwitchAccessToken(userId: string, token: Partial) { - return db.twitchAccessToken.update({ - where: { userId }, - data: { - ...token, - obtainmentTimestamp: token.obtainmentTimestamp?.toString(), - }, - }) - } - - createTwitchAccessToken(token: TwitchAccessToken) { - return db.twitchAccessToken.create({ - data: { - ...token, - obtainmentTimestamp: token.obtainmentTimestamp.toString(), - }, - }) - } - - findVillage() { - return db.village.findFirst() - } - - async findTopPlayers() { - const famous = await db.player.findFirst({ - orderBy: { reputation: 'desc' }, - }) - const rich = await db.player.findFirst({ - orderBy: { coins: 'desc' }, - }) - const viewer = await db.player.findFirst({ - orderBy: { viewerPoints: 'desc' }, - }) - const raider = await db.player.findFirst({ - orderBy: { raiderPoints: 'desc' }, - }) - const villain = await db.player.findFirst({ - orderBy: { villainPoints: 'desc' }, - }) - const refueller = await db.player.findFirst({ - orderBy: { refuellerPoints: 'desc' }, - }) - const woodsman = await this.findTopWoodsmanPlayer() - const miner = await this.findTopMinerPlayer() - - return { - famous: { - player: famous, - points: famous?.reputation, - }, - rich: { - player: rich, - points: rich?.coins, - }, - viewer: { - player: viewer, - points: viewer?.viewerPoints, - }, - raider: { - player: raider, - points: raider?.raiderPoints, - }, - villain: { - player: villain, - points: villain?.villainPoints, - }, - refueller: { - player: refueller, - points: refueller?.refuellerPoints, - }, - woodsman, - miner, - } - } - - async findTopWoodsmanPlayer() { - const topSkill = await db.skill.findFirst({ - where: { type: 'WOODSMAN' }, - orderBy: { lvl: 'desc' }, - }) - if (!topSkill) { - return null - } - const player = await db.player.findUnique({ - where: { id: topSkill.objectId }, - }) - - return { - player, - points: topSkill.lvl, - } - } - - async findTopMinerPlayer() { - const topSkill = await db.skill.findFirst({ - where: { type: 'MINER' }, - orderBy: { lvl: 'desc' }, - }) - if (!topSkill) { - return null - } - const player = await db.player.findUnique({ - where: { id: topSkill.objectId }, - }) - - return { - player, - points: topSkill.lvl, - } - } - - findPlayerByTwitchId(twitchId: string) { - return this.db.player.findFirst({ - where: { twitchId }, - }) - } - - createPlayer({ - twitchId, - userName, - inventoryId, - id, - }: { - twitchId: string - userName: string - inventoryId: string - id: string - }) { - return db.player.create({ - data: { - id, - twitchId, - userName, - inventoryId, - }, - }) - } - - async findOrCreatePlayer(twitchId: string, userName: string) { - const player = await this.findPlayerByTwitchId(twitchId) - if (!player) { - const id = createId() - const inventory = await this.createInventory(id) - return this.createPlayer({ - id, - twitchId, - userName, - inventoryId: inventory.id, - }) - } - - return player - } - - createInventory(objectId: string) { - return db.inventory.create({ - data: { - id: createId(), - objectId, - }, - }) - } - - addPlayerViewerPoints(id: string, increment: number) { - return db.player.update({ - where: { id }, - data: { - viewerPoints: { increment }, - }, - }) - } - - findAllChatCommands() { - return db.chatCommand.findMany() - } -} diff --git a/apps/api/src/game/actions/action.ts b/apps/api/src/game/actions/action.ts deleted file mode 100644 index 9bf94c9e..00000000 --- a/apps/api/src/game/actions/action.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { - IGameAction, - IGameActionResponse, -} from '../../../../../packages/api-sdk/src' -import type { Player } from '../objects/units' - -interface IActionOptions { - command: IGameAction['command'] - commandDescription: IGameAction['commandDescription'] -} - -export class Action implements IGameAction { - public command: string - public commandDescription: string - - public live!: ( - player: Player, - params: string[], - ) => Promise - - constructor({ command, commandDescription }: IActionOptions) { - this.command = command - this.commandDescription = commandDescription - } -} diff --git a/apps/api/src/game/actions/donateWoodToVillageAction.ts b/apps/api/src/game/actions/donateWoodToVillageAction.ts deleted file mode 100644 index 073b40dc..00000000 --- a/apps/api/src/game/actions/donateWoodToVillageAction.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ANSWER } from '../../../../../packages/api-sdk/src/lib/actionAnswer' -import { Village } from '../chunks' -import type { Warehouse } from '../objects/buildings/warehouse' -import type { Player } from '../objects/units' -import type { GameScene } from '../scenes' -import { Action } from './action' - -interface IDonateWoodToVillageActionOptions { - scene: GameScene -} - -export class DonateWoodToVillageAction extends Action { - private scene: GameScene - - constructor({ scene }: IDonateWoodToVillageActionOptions) { - super({ command: 'donate', commandDescription: '!donate [quantity]' }) - - this.scene = scene - this.live = this.initLive - } - - async initLive(player: Player, params: string[]) { - const amount = this.scene.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 isSuccess = await player.inventory.reduceOrDestroyItem('WOOD', amount) - if (!isSuccess) { - return ANSWER.NOT_ENOUGH_WOOD_ERROR - } - - await warehouse?.inventory.addOrCreateItem('WOOD', amount) - await player.addReputation(amount) - - return ANSWER.DONATE_WOOD_OK - } -} diff --git a/apps/api/src/game/actions/plantTreeAction.ts b/apps/api/src/game/actions/plantTreeAction.ts deleted file mode 100644 index f9defd7b..00000000 --- a/apps/api/src/game/actions/plantTreeAction.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ANSWER } from '../../../../../packages/api-sdk/src/lib/actionAnswer' -import { Village } from '../chunks' -import type { Player } from '../objects/units' -import type { GameScene } from '../scenes' -import { PlantNewTreeScript } from '../scripts/plantNewTreeScript' -import { Action } from './action' - -interface IPlantTreeActionOptions { - scene: GameScene -} - -export class PlantTreeAction extends Action { - private scene: GameScene - - constructor({ scene }: IPlantTreeActionOptions) { - super({ command: 'plant', commandDescription: '!plant' }) - - this.scene = scene - this.live = this.initLive - } - - async initLive(player: Player) { - if (player.script && !player.script.isInterruptible) { - return ANSWER.BUSY_ERROR - } - - if (this.scene.chunkNow instanceof Village) { - const target = this.scene.chunkNow.checkIfNeedToPlantTree() - if (!target) { - return ANSWER.NO_SPACE_AVAILABLE_ERROR - } - - const plantNewTreeFunc = () => { - if (this.scene.chunkNow instanceof Village) { - this.scene.chunkNow.plantNewTree(target) - } - } - - player.script = new PlantNewTreeScript({ - object: player, - target, - plantNewTreeFunc, - }) - - return ANSWER.OK - } - - return ANSWER.ERROR - } -} diff --git a/apps/api/src/game/actions/voteAction.ts b/apps/api/src/game/actions/voteAction.ts deleted file mode 100644 index 5e78c785..00000000 --- a/apps/api/src/game/actions/voteAction.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ANSWER } from '../../../../../packages/api-sdk/src/lib/actionAnswer' -import type { Poll } from '../common' -import type { Player } from '../objects/units' -import { Action } from './action' - -interface IVoteActionOptions { - poll: Poll -} - -export class VoteAction extends Action { - private poll: Poll - private readonly id: string - - constructor({ poll }: IVoteActionOptions) { - super({ command: 'go', commandDescription: '!go' }) - - this.id = poll.generatePollId() - - this.command = `go ${this.id}` - this.commandDescription = `!go ${this.id}` - - this.poll = poll - this.live = this.initLive - } - - async initLive(player: Player) { - const isSuccess = this.poll.vote(player) - if (!isSuccess) { - return ANSWER.ALREADY_VOTED_ERROR - } - - return ANSWER.VOTED_OK - } -} diff --git a/apps/api/src/game/chunks/forest.ts b/apps/api/src/game/chunks/forest.ts deleted file mode 100644 index 0b35d64f..00000000 --- a/apps/api/src/game/chunks/forest.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - type IGameChunkTheme, - type IGameForestChunk, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Stone, Tree } from '../objects' -import { GameChunk } from './gameChunk' - -interface IForestOptions { - center: IGameForestChunk['center'] - width: number - height: number - theme: IGameChunkTheme -} - -export class Forest extends GameChunk implements IGameForestChunk { - constructor({ width, height, center, theme }: IForestOptions) { - super({ - title: 'Grand Wood', - type: 'FOREST', - width, - height, - center, - theme, - }) - - 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({ - 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({ x: point.x, y: point.y, resource: 1 }) - this.objects.push(stone) - } - } -} diff --git a/apps/api/src/game/chunks/gameChunk.ts b/apps/api/src/game/chunks/gameChunk.ts deleted file mode 100644 index 0441aba1..00000000 --- a/apps/api/src/game/chunks/gameChunk.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameChunk, - type IGameChunkTheme, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Area, type GameObject, Tree } from '../objects' - -interface IGameChunkOptions { - center: IGameChunk['center'] - title: IGameChunk['title'] - type: IGameChunk['type'] - theme: IGameChunkTheme - width: number - height: number -} - -export class GameChunk implements IGameChunk { - public id: string - public title: string - public type: IGameChunk['type'] - public center!: IGameChunk['center'] - public area!: Area - public isVisibleOnClient = false - - public needToSendDataToClient: boolean - public objects: GameObject[] = [] - - constructor({ - title, - type, - theme, - width, - height, - center, - }: IGameChunkOptions) { - this.id = createId() - this.center = center - this.title = title - this.type = type - - this.needToSendDataToClient = false - - this.initArea({ width, height, theme }) - } - - private initArea({ - width, - height, - theme, - }: { - width: number - height: number - theme: IGameChunkTheme - }) { - const halfWidth = Math.round(width / 2) - const halfHeight = Math.round(height / 2) - - const area = { - startX: this.center.x - halfWidth, - endX: this.center.x + halfWidth, - startY: this.center.y - halfHeight, - endY: this.center.y + halfHeight, - } - - this.area = new Area({ theme, area }) - } - - public live() { - for (const obj of this.objects) { - obj.isVisibleOnClient = this.isVisibleOnClient - obj.needToSendDataToClient = this.needToSendDataToClient - } - this.area.isVisibleOnClient = this.isVisibleOnClient - this.area.needToSendDataToClient = this.needToSendDataToClient - this.area.live() - } - - public getRandomPoint() { - return { - x: getRandomInRange(this.area.area.startX, this.area.area.endX), - y: getRandomInRange(this.area.area.startY, this.area.area.endY), - } - } - - public getRandomOutPoint() { - const height = this.area.area.endY - this.area.area.startY - const offsetFromTop = Math.round(height / 4) - - return { - x: this.area.area.endX, - y: getRandomInRange( - this.area.area.startY + offsetFromTop, - this.area.area.endY, - ), - } - } - - public checkIfPointIsInArea(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 - } - } - - return false - } - - removeObject(object: GameObject) { - const index = this.objects.indexOf(object) - this.objects.splice(index, 1) - } - - getAvailableTree(): Tree | undefined { - const trees = this.objects.filter( - (obj) => - obj instanceof Tree - && obj.state !== 'DESTROYED' - && !obj.isReserved - && obj.isReadyToChop, - ) - if (!trees || !trees.length) { - return undefined - } - - return trees[Math.floor(Math.random() * trees.length)] as Tree - } -} diff --git a/apps/api/src/game/chunks/index.ts b/apps/api/src/game/chunks/index.ts deleted file mode 100644 index 7c89cad5..00000000 --- a/apps/api/src/game/chunks/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { Forest } from './forest' -export { GameChunk } from './gameChunk' -export { Village } from './village' -export { LakeChunk } from './lake' diff --git a/apps/api/src/game/chunks/lake.ts b/apps/api/src/game/chunks/lake.ts deleted file mode 100644 index ab701dc0..00000000 --- a/apps/api/src/game/chunks/lake.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - type IGameChunkTheme, - type IGameLakeChunk, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Lake, Stone, Tree } from '../objects' -import { GameChunk } from './gameChunk' - -interface ILakeOptions { - center: IGameLakeChunk['center'] - width: number - height: number - theme: IGameChunkTheme -} - -export class LakeChunk extends GameChunk implements IGameLakeChunk { - constructor({ width, height, center, theme }: ILakeOptions) { - super({ - title: 'Lake with a Secret', - type: 'LAKE', - width, - height, - center, - theme, - }) - - const treesToPrepare = Math.round( - (this.area.area.endX - this.area.area.startX) / 30, - ) - 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({ x: this.center.x - 100, y: this.center.y + 400 }) - const lake2 = new Lake({ x: this.center.x - 600, y: this.center.y + 500 }) - this.objects.push(lake, lake2) - } - - initTrees(count: number) { - for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() - const size = getRandomInRange(75, 90) - const tree = new Tree({ - 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({ x: point.x, y: point.y, resource: 1 }) - this.objects.push(stone) - } - } -} diff --git a/apps/api/src/game/chunks/village.ts b/apps/api/src/game/chunks/village.ts deleted file mode 100644 index b2a45bbf..00000000 --- a/apps/api/src/game/chunks/village.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { - type IGameChunkTheme, - type IGameObjectFlag, - type IGameVillageChunk, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Flag, Stone, Tree } from '../objects' -import { Building } from '../objects/buildings/building' -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 { VillageCourier, VillageFarmer } 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' - -interface IVillageOptions { - width: number - height: number - center: IGameVillageChunk['center'] - theme: IGameChunkTheme -} - -export class Village extends GameChunk implements IGameVillageChunk { - constructor({ width, height, center, theme }: IVillageOptions) { - super({ title: '', type: 'VILLAGE', theme, width, height, center }) - - this.title = this.getRandomTitle() - - this.initFlags('RESOURCE', 80) - this.initFlags('MOVEMENT', 30) - this.initTrees(20) - this.initStones(5) - - this.initCourier(3) - this.initFarmer(3) - this.initBuildings() - } - - live() { - super.live() - - for (const object of this.objects) { - if (object instanceof VillageFarmer && !object.script) { - this.addTaskToFarmer(object) - continue - } - - if (object instanceof VillageCourier && !object.script) { - this.addTaskToCourier(object) - continue - } - - void object.live() - } - } - - addTaskToCourier(object: VillageCourier) { - const random = getRandomInRange(1, 200) - if (random !== 1) { - return - } - - // Need to build Store - const warehouse = this.getWarehouse() - const store = this.getStore() - const wood = warehouse?.getItemByType('WOOD') - if (wood?.amount && wood.amount >= 25 && !store) { - // Let's build! - const target = this.getConstructionArea() - if (!target) { - return - } - - const buildFunc = (): boolean => { - warehouse?.inventory.reduceOrDestroyItem('WOOD', 25) - this.buildStore() - - return true - } - object.script = new BuildScript({ - object, - target, - buildFunc, - }) - - return - } - - // If unit have smth in inventory - const item = object.inventory.checkIfAlreadyHaveItem('WOOD') - if (item) { - const target = this.getWarehouse() - if (!target) { - return - } - - const placeItemFunc = () => { - if (object.target instanceof Building) { - void object.target.inventory.addOrCreateItem(item.type, item.amount) - void object.inventory.destroyItem(item.id) - } - } - object.script = new PlaceItemInWarehouseScript({ - object, - target, - placeItemFunc, - }) - - return - } - - // If there is an available tree - const availableTree = this.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 - } - - const target = this.getRandomMovementFlagInVillage() - if (!target) { - return - } - object.script = new MoveToTargetScript({ - object, - target, - }) - } - - addTaskToFarmer(object: VillageFarmer) { - const target = this.checkIfNeedToPlantTree() - if (target) { - const plantNewTreeFunc = () => { - this.plantNewTree(target) - } - - object.script = new PlantNewTreeScript({ - object, - target, - plantNewTreeFunc, - }) - return - } - - // No Trees needed? - const random = getRandomInRange(1, 300) - if (random <= 1) { - const target = this.getRandomMovementFlagInVillage() - if (!target) { - return - } - object.script = new MoveToTargetScript({ - object, - target, - }) - } - } - - initFlag(type: IGameObjectFlag['type']) { - const randomPoint = this.getRandomPoint() - this.objects.push(new Flag({ type, x: randomPoint.x, y: randomPoint.y })) - } - - initFlags(type: IGameObjectFlag['type'], count: number) { - for (let i = 0; i < count; i++) { - this.initFlag(type) - } - } - - initTrees(count: number) { - for (let i = 0; i < count; i++) { - const flag = this.getRandomEmptyResourceFlagInVillage() - if (flag) { - const size = getRandomInRange(75, 90) - const tree = new Tree({ - x: flag.x, - y: flag.y, - size, - resource: 1, - health: 20, - variant: this.area.theme, - }) - flag.target = tree - this.objects.push(tree) - } - } - } - - initStones(count: number) { - for (let i = 0; i < count; i++) { - const flag = this.getRandomEmptyResourceFlagInVillage() - if (flag) { - const stone = new Stone({ x: flag.x, y: flag.y, resource: 1 }) - flag.target = stone - this.objects.push(stone) - } - } - } - - initCourier(count = 1) { - for (let i = 0; i < count; i++) { - const randomPoint = this.getRandomPoint() - this.objects.push( - new VillageCourier({ - x: randomPoint.x, - y: randomPoint.y, - }), - ) - } - } - - initFarmer(count = 1) { - for (let i = 0; i < count; i++) { - const randomPoint = this.getRandomPoint() - this.objects.push( - new VillageFarmer({ - x: randomPoint.x, - y: randomPoint.y, - }), - ) - } - } - - initBuildings() { - this.objects.push( - new Campfire({ - x: this.center.x, - y: this.center.y, - }), - ) - this.objects.push( - new Warehouse({ - x: this.center.x + 270, - y: this.center.y - 150, - }), - ) - this.objects.push( - new WagonStop({ - x: this.center.x - 780, - y: this.center.y + 380, - }), - ) - this.objects.push( - new ConstructionArea({ - x: this.center.x + 600, - y: this.center.y + 250, - }), - ) - } - - buildStore() { - const constructionArea = this.getConstructionArea() - if (!constructionArea) { - return - } - - constructionArea.state = 'DESTROYED' - constructionArea.handleChange() - - this.removeObject(constructionArea) - this.objects.push( - new Store({ - 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 } - } - - getRandomEmptyResourceFlagInVillage() { - const flags = this.objects.filter( - (f) => - f instanceof Flag - && f.type === 'RESOURCE' - && !f.target - && !f.isReserved, - ) - return flags.length > 0 - ? (flags[Math.floor(Math.random() * flags.length)] as Flag) - : undefined - } - - getResourceFlagInVillageAmount() { - return this.objects.filter( - (f) => f instanceof Flag && f.type === 'RESOURCE', - ).length - } - - getRandomMovementFlagInVillage() { - const flags = this.objects.filter( - (f) => f instanceof Flag && f.type === 'MOVEMENT', - ) - return flags.length > 0 - ? flags[Math.floor(Math.random() * flags.length)] - : undefined - } - - getRandomTitle() { - const titles = [ - 'Windy Peak', - 'Green Grove', - 'Oak Coast', - 'Forest Harbor', - 'Elven Forest', - 'Stone Outpost', - 'Watermelon Paradise', - 'Magic Valley', - 'Royal Haven', - 'Phantom Cliff', - ] - return titles[Math.floor(Math.random() * titles.length)] - } - - checkIfNeedToPlantTree() { - const treesNow = this.objects.filter( - (t) => t instanceof Tree && t.state !== 'DESTROYED', - ) - if (treesNow.length < 40) { - return this.getRandomEmptyResourceFlagInVillage() - } - } - - plantNewTree(flag: Flag) { - const tree = new Tree({ - x: flag.x, - y: flag.y, - resource: 1, - size: 12, - health: 20, - variant: this.area.theme, - }) - - flag.target = tree - flag.isReserved = false - this.objects.push(tree) - } - - getTreesAmount() { - return this.objects.filter( - (obj) => obj instanceof Tree && obj.state !== 'DESTROYED', - ).length - } - - checkIfThereAreNotEnoughTrees() { - const max = this.getResourceFlagInVillageAmount() - const now = this.getTreesAmount() - - return now < max / 3 - } -} diff --git a/apps/api/src/game/common/event.ts b/apps/api/src/game/common/event.ts deleted file mode 100644 index 2bb771a8..00000000 --- a/apps/api/src/game/common/event.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type GameSceneType, - type IGameEvent, - getDatePlusSeconds, -} from '../../../../../packages/api-sdk/src' -import { sendMessage } from '../../websocket/websocket.server' - -interface IEventOptions { - title: IGameEvent['title'] - description: IGameEvent['description'] - type: IGameEvent['type'] - secondsToEnd: number - scene?: GameSceneType - poll: IGameEvent['poll'] - quest: IGameEvent['quest'] - offers: IGameEvent['offers'] -} - -export class Event implements IGameEvent { - public id: string - public title: IGameEvent['title'] - public description: IGameEvent['description'] - public type: IGameEvent['type'] - public status: IGameEvent['status'] - public scene?: GameSceneType - public endsAt!: Date - public deletesAt!: Date - public poll?: IGameEvent['poll'] - public quest?: IGameEvent['quest'] - public offers?: IGameEvent['offers'] - - constructor({ - title, - description, - type, - secondsToEnd, - scene, - poll, - quest, - offers, - }: IEventOptions) { - this.id = createId() - this.title = title - this.description = description - this.type = type - this.scene = scene - this.poll = poll - this.quest = quest - this.offers = offers - this.status = 'STARTED' - - this.setEndsAtPlusSeconds(secondsToEnd) - - sendMessage(type) - } - - public checkStatus() { - if (this.endsAt.getTime() <= new Date().getTime()) { - this.status = 'STOPPED' - } - if (this.deletesAt.getTime() <= new Date().getTime()) { - this.status = 'STOPPED' - } - - return this.status - } - - public setEndsAtPlusSeconds(seconds: number) { - this.endsAt = getDatePlusSeconds(seconds) - this.deletesAt = getDatePlusSeconds(seconds + 30) - } -} diff --git a/apps/api/src/game/common/generators/unitName.ts b/apps/api/src/game/common/generators/unitName.ts deleted file mode 100644 index 52d8b4cd..00000000 --- a/apps/api/src/game/common/generators/unitName.ts +++ /dev/null @@ -1,29 +0,0 @@ -export function generateUnitUserName(): string { - const maleNames = [ - 'Valto Bizu', - 'Zapris Hlel', - 'Sinkmire Winglace', - 'Hakir Elisor', - 'Mapitu Uldan', - 'Aetes Shangueiros', - 'Nemesion Balgran', - 'Garffon Lisalor', - 'Golash Aena', - 'Alistair al Pair', - 'Sasgix Eranal', - 'Petrosque Quinal', - 'Laegon Umeran', - 'Hersperon Oderle', - 'Callister Grafft', - 'Zangard Kaalin', - 'Xernes Adafin', - 'Xanus Elhora', - 'Alistair Chapira', - 'Salvestro Elmaris', - 'Elusidor Riecto', - 'Usir Lierin', - 'Golash Yosalr', - ] - - return maleNames[Math.floor(Math.random() * maleNames.length)] -} diff --git a/apps/api/src/game/common/generators/unitTop.ts b/apps/api/src/game/common/generators/unitTop.ts deleted file mode 100644 index caffe7f1..00000000 --- a/apps/api/src/game/common/generators/unitTop.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { IGameObjectUnit } from '../../../../../../packages/api-sdk/src' - -export function generateUnitTop(): Partial< - IGameObjectUnit['visual']['top'] -> { - const availableTopsForUnits: IGameObjectUnit['visual']['top'][] = [ - 'GREEN_SHIRT', - 'BLUE_SHIRT', - 'DARK_SILVER_SHIRT', - ] - - return availableTopsForUnits[ - Math.floor(Math.random() * availableTopsForUnits.length) - ] -} diff --git a/apps/api/src/game/common/group.ts b/apps/api/src/game/common/group.ts deleted file mode 100644 index 8d67ef6e..00000000 --- a/apps/api/src/game/common/group.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { - IGameGroup, - IGameObjectPlayer, -} from '../../../../../packages/api-sdk/src' -import type { Player } from '../objects/units' - -export class Group implements IGameGroup { - id: string - players: IGameObjectPlayer[] = [] - - constructor() { - this.id = createId() - } - - public getGroup(): IGameGroup { - return { - id: this.id, - players: this.players.map((p) => { - return { - ...p, - script: undefined, - live: undefined, - } - }), - } - } - - join(player: Player): boolean { - const check = this.findPlayer(player.id) - if (check) { - return false - } - - this.players.push(player) - return true - } - - remove(player: IGameObjectPlayer): boolean { - const check = this.findPlayer(player.id) - if (!check) { - return false - } - - const index = this.players.indexOf(player) - this.players.splice(index, 1) - return true - } - - findPlayer(id: string) { - return this.players.find((p) => p.id === id) - } - - disband() { - this.players = [] - } -} diff --git a/apps/api/src/game/common/index.ts b/apps/api/src/game/common/index.ts deleted file mode 100644 index aacc870c..00000000 --- a/apps/api/src/game/common/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { Inventory } from './inventory' -export { Skill } from './skill' -export { Event } from './event' -export { Group } from './group' -export { Route } from './route' -export { Poll } from './poll' diff --git a/apps/api/src/game/common/inventory.ts b/apps/api/src/game/common/inventory.ts deleted file mode 100644 index 63e43a5a..00000000 --- a/apps/api/src/game/common/inventory.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { - IGameInventory, - IGameInventoryItem, - ItemType, -} from '../../../../../packages/api-sdk/src' -import { db } from '../../db/db.client' - -interface IInventoryOptions { - objectId: string - id: string - saveInDb: boolean -} - -export class Inventory implements IGameInventory { - public id: string - public objectId: string - public items: IGameInventoryItem[] = [] - public saveInDb: boolean - - constructor({ id, objectId, saveInDb }: IInventoryOptions) { - this.id = id - this.objectId = objectId - this.saveInDb = saveInDb - } - - public async init() { - await this.updateFromDB() - } - - public async destroyItem(id: string) { - const itemIndex = this.items.findIndex((i) => i.id === id) - if (itemIndex < 0) { - return - } - - this.items.splice(itemIndex, 1) - - if (this.saveInDb) { - await this.destroyItemInDB(id) - } - } - - public async reduceOrDestroyItem(type: ItemType, amount: number) { - const item = this.checkIfAlreadyHaveItem(type) - if (!item) { - return false - } - - if (amount > item.amount) { - return false - } - - if (amount === item.amount) { - await this.destroyItem(item.id) - return true - } - - if (this.saveInDb) { - await this.decrementAmountOfItemInDB(item.id, amount) - } - - item.amount -= amount - return true - } - - public async addOrCreateItem(type: ItemType, amount: number) { - if (this.saveInDb) { - return this.addOrCreateItemInDB(type, amount) - } - - const item = this.checkIfAlreadyHaveItem(type) - if (!item) { - this.createItem(type, amount) - return - } - - item.amount += amount - } - - async addOrCreateItemInDB(type: ItemType, amount: number) { - const item = await this.checkIfAlreadyHaveItemInDB(this.id, type) - if (!item) { - await this.createItemInDB(this.id, type, amount) - await this.updateFromDB() - return - } - - await this.incrementAmountOfItemInDB(item.id, amount) - await this.updateFromDB() - } - - async destroyItemInDB(id: string) { - await db.inventoryItem.delete({ - where: { id }, - }) - await this.updateFromDB() - } - - public tryGetItemInDB(type: ItemType) { - return this.checkIfAlreadyHaveItemInDB(this.id, type) - } - - async checkAndBreakItem(item: IGameInventoryItem, decrement: number) { - if (item.durability <= decrement) { - await this.destroyItemInDB(item.id) - await this.updateFromDB() - return - } - - item.durability -= decrement - await db.inventoryItem.update({ - where: { id: item.id }, - data: { - durability: { decrement }, - }, - }) - } - - createItemInDB(inventoryId: string, type: ItemType, amount: number) { - return db.inventoryItem.create({ - data: { - id: createId(), - type, - inventoryId, - amount, - }, - }) - } - - incrementAmountOfItemInDB(id: string, amount: number) { - return db.inventoryItem.update({ - where: { id }, - data: { - amount: { - increment: amount, - }, - }, - }) - } - - decrementAmountOfItemInDB(id: string, amount: number) { - return db.inventoryItem.update({ - where: { id }, - data: { - amount: { - decrement: amount, - }, - }, - }) - } - - checkIfAlreadyHaveItemInDB(inventoryId: string, type: ItemType) { - return db.inventoryItem.findFirst({ - where: { inventoryId, type }, - }) - } - - checkIfAlreadyHaveItem(type: ItemType) { - return this.items.find((item) => item.type === type) - } - - createItem(type: ItemType, amount: number) { - const item: IGameInventoryItem = { - id: createId(), - createdAt: new Date(), - updatedAt: new Date(), - type, - amount, - durability: 100, - inventoryId: '', - } - this.items.push(item) - } - - async updateFromDB() { - const items = await db.inventoryItem.findMany({ - where: { inventoryId: this.id }, - }) - this.items = items as IGameInventoryItem[] - } -} diff --git a/apps/api/src/game/common/poll.ts b/apps/api/src/game/common/poll.ts deleted file mode 100644 index 440dd979..00000000 --- a/apps/api/src/game/common/poll.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectPlayer, - type IGamePoll, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { VoteAction } from '../actions/voteAction' -import type { GameScene } from '../scenes' - -interface IPollOptions { - scene: GameScene - votesToSuccess: IGamePoll['votesToSuccess'] -} - -export class Poll implements IGamePoll { - public id: string - public status: IGamePoll['status'] - public action: IGamePoll['action'] - public votesToSuccess: IGamePoll['votesToSuccess'] - public votes: IGamePoll['votes'] = [] - - public scene: GameScene - - constructor({ votesToSuccess, scene }: IPollOptions) { - this.scene = scene - - this.id = createId() - this.status = 'ACTIVE' - this.votesToSuccess = votesToSuccess - - this.action = new VoteAction({ poll: this }) - } - - public vote(player: IGameObjectPlayer): boolean { - if (this.votes.find((v) => v.id === player.id)) { - return false - } - - this.votes.push({ id: player.id, userName: player.userName }) - return true - } - - public generatePollId(): string { - const id = getRandomInRange(1, 9).toString() - for (const event of this.scene.eventService.events) { - if (event.poll?.action.command === `go ${id}`) { - return this.generatePollId() - } - } - return id - } -} diff --git a/apps/api/src/game/common/route.ts b/apps/api/src/game/common/route.ts deleted file mode 100644 index a2b0a911..00000000 --- a/apps/api/src/game/common/route.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { - IGameChunk, - IGameRoute, -} from '../../../../../packages/api-sdk/src' -import { Flag } from '../objects' - -interface IRoutePoint { - x: number - y: number -} - -interface IRouteArea { - startX: number - endX: number - startY: number - endY: number -} - -export class Route implements IGameRoute { - public startPoint!: IRoutePoint - public endPoint!: IRoutePoint - public chunks: IGameChunk[] = [] - - public flags: Flag[] = [] - public areas: IRouteArea[] = [] - - public addChunk(chunk: IGameChunk) { - this.chunks.push({ - id: chunk.id, - type: chunk.type, - title: chunk.title, - center: chunk.center, - area: chunk.area, - isVisibleOnClient: chunk.isVisibleOnClient, - }) - } - - public setEndPoint({ x, y }: IRoutePoint) { - this.endPoint = { x, y } - } - - public addFlag({ x, y }: IRoutePoint) { - const movementFlag = new Flag({ type: 'WAGON_MOVEMENT', x, y }) - - const prevFlag = this.flags[this.flags.length - 1] - if (prevFlag) { - this.initArea(prevFlag, movementFlag) - } - - this.flags.push(movementFlag) - } - - public addGlobalFlag(end: IRoutePoint) { - const prevGlobalFlag = this.flags[this.flags.length - 1] - if (!prevGlobalFlag) { - return this.addFlag(end) - } - - this.generatePath({ x: prevGlobalFlag.x, y: prevGlobalFlag.y }, end) - this.addFlag({ x: end.x, y: end.y }) - } - - public getNextFlag() { - return this.flags[0] - } - - public removeFlag(flag: Flag) { - const index = this.flags.findIndex((f) => f.id === flag.id) - if (index >= 0) { - this.flags.splice(index, 1) - } - } - - public initArea(flag1: Flag, flag2: Flag) { - const offset = 150 - const halfOffset = offset / 2 - - const startX = Math.min(flag1.x, flag2.x) - offset - const endX = Math.max(flag1.x, flag2.x) + offset - - const startY = Math.min(flag1.y, flag2.y) - halfOffset - const endY = Math.max(flag1.y, flag2.y) + halfOffset - - const area = { - startX, - endX, - startY, - endY, - } - - this.areas.push(area) - } - - private isInArea(area: IRouteArea, point: IRoutePoint) { - return ( - area.startX < point.x - && point.x < area.endX - && area.startY < point.y - && point.y < area.endY - ) - } - - public checkIfPointIsOnWagonPath(point: IRoutePoint) { - for (const area of this.areas) { - if (this.isInArea(area, point)) { - return true - } - } - - return false - } - - generatePath(start: IRoutePoint, end: IRoutePoint) { - const pathDistance = Route.getDistanceBetween2Points(start, end) - console.log('path', pathDistance) - - const pointsCount = Math.round(pathDistance / 150) + 1 - console.log('points between', pointsCount) - - const stepX = Math.round((end.x - start.x) / pointsCount) - const stepY = Math.round((end.y - start.y) / pointsCount) - - let nowX = start.x - let nowY = start.y - - for (let i = 0; i < pointsCount; i++) { - nowX += stepX - nowY += stepY - this.addFlag({ x: nowX, y: nowY }) - } - } - - public static getDistanceBetween2Points( - point1: { - x: number - y: number - }, - point2: { - x: number - y: number - }, - ) { - return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2) - } -} diff --git a/apps/api/src/game/common/skill.ts b/apps/api/src/game/common/skill.ts deleted file mode 100644 index 8c269073..00000000 --- a/apps/api/src/game/common/skill.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameSkill } from '../../../../../packages/api-sdk/src' -import { db } from '../../db/db.client' - -interface ISkillOptions { - id: string -} - -export class Skill implements IGameSkill { - public id: string - public objectId: string | null = null - public type!: IGameSkill['type'] - - public lvl = 0 - public xp = 0 - public xpNextLvl = 0 - - constructor({ id }: ISkillOptions) { - this.id = id - } - - public async init() { - await this.readFromDB() - } - - public async addXp(increment = 1) { - this.xp += increment - - if (this.xp >= this.xpNextLvl) { - await this.lvlUpInDB() - await this.init() - return - } - - return db.skill.update({ - where: { id: this.id }, - data: { xp: { increment } }, - }) - } - - public lvlUpInDB() { - const xpNextLvl = Math.floor(this.xpNextLvl * 1.5) - return db.skill.update({ - where: { id: this.id }, - data: { - lvl: { increment: 1 }, - xp: 0, - xpNextLvl, - }, - }) - } - - async readFromDB() { - const skill = await db.skill.findUnique({ - where: { id: this.id }, - }) - if (!skill) { - return - } - - this.objectId = skill.objectId - this.type = skill.type as IGameSkill['type'] - this.lvl = skill.lvl - this.xp = skill.xp - this.xpNextLvl = skill.xpNextLvl - } - - public static async findAllInDB(objectId: string) { - return db.skill.findMany({ - where: { objectId }, - }) - } - - public static createInDB(objectId: string, type: IGameSkill['type']) { - return db.skill.create({ - data: { - id: createId(), - objectId, - type, - }, - }) - } -} diff --git a/apps/api/src/game/game.ts b/apps/api/src/game/game.ts deleted file mode 100644 index 076413e5..00000000 --- a/apps/api/src/game/game.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { - GameSceneType, - IGameActionResponse, - IGameSceneAction, -} from '../../../../packages/api-sdk/src' -import { DBRepository } from '../db/db.repository' -import { sendMessage } from '../websocket/websocket.server' -import type { Action } from './actions/action' -import { - DefenceScene, - type GameScene, - MovingScene, - VillageScene, -} from './scenes' - -interface HandleChatCommandOptions { - action: IGameSceneAction - userId: string // Twitch - userName: string // Twitch - params?: string[] // May have viewersCount or text -} - -interface HandleChatCommandResponse { - ok: boolean - message: string | null -} - -export class Game { - public repository: DBRepository - public scene!: GameScene - - constructor() { - this.repository = new DBRepository() - - this.initScene('MOVING') - } - - public async handleActionFromChat({ - action, - userId, - userName, - params, - }: HandleChatCommandOptions): Promise { - const player = await this.repository.findOrCreatePlayer(userId, userName) - - return this.scene.actionService.handleAction(action, player.id, params) - } - - public async handleDynamicActionFromChat({ - action, - userId, - userName, - params, - }: { - action: Action - userId: string - userName: string - params: string[] - }): Promise { - const player = await this.repository.findOrCreatePlayer(userId, userName) - - return this.scene.actionService.handleDynamicAction( - action, - player.id, - params, - ) - } - - public initScene(scene: GameSceneType) { - if (this.scene) { - this.scene.destroy() - } - sendMessage('SCENE_CHANGED') - - if (scene === 'MOVING') { - this.scene = new MovingScene({ game: this }) - return - } - if (scene === 'VILLAGE') { - this.scene = new VillageScene({ game: this }) - return - } - if (scene === 'DEFENCE') { - this.scene = new DefenceScene({ game: this }) - } - } -} diff --git a/apps/api/src/game/objects/area.ts b/apps/api/src/game/objects/area.ts deleted file mode 100644 index d68739a6..00000000 --- a/apps/api/src/game/objects/area.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectArea, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { GameObject } from './gameObject' - -interface IAreaOptions { - theme: IGameObjectArea['theme'] - area: IGameObjectArea['area'] -} - -export class Area extends GameObject implements IGameObjectArea { - theme: IGameObjectArea['theme'] - area: IGameObjectArea['area'] - - constructor({ theme, area }: IAreaOptions) { - const id = createId() - const x = area.startX - const y = area.startY - - super({ id, x, y, entity: 'AREA' }) - - this.theme = theme - this.area = area - } - - live() { - if (this.state === 'IDLE') { - const random = getRandomInRange(1, 500) - if (random <= 1) { - this.handleChange() - } - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } -} diff --git a/apps/api/src/game/objects/buildings/building.ts b/apps/api/src/game/objects/buildings/building.ts deleted file mode 100644 index 9a9d962d..00000000 --- a/apps/api/src/game/objects/buildings/building.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { - IGameObjectBuilding, - IGameObjectBuildingType, - ItemType, -} from '../../../../../../packages/api-sdk/src' -import { Inventory } from '../../common' -import { GameObject } from '../gameObject' - -interface IBuildingOptions { - entity: IGameObjectBuildingType - x: number - y: number -} - -export class Building extends GameObject implements IGameObjectBuilding { - public inventory!: Inventory - - constructor({ entity, x, y }: IBuildingOptions) { - const finalId = createId() - - super({ id: finalId, x, y, entity }) - - this.initInventory() - } - - live() { - this.handleChange() - } - - handleChange() { - this.sendMessageObjectUpdated() - } - - public initInventory() { - this.inventory = new Inventory({ - objectId: this.id, - id: createId(), - saveInDb: false, - }) - } - - public getItemByType(type: ItemType) { - return this.inventory.items.find((item) => item.type === type) - } -} diff --git a/apps/api/src/game/objects/buildings/campfire.ts b/apps/api/src/game/objects/buildings/campfire.ts deleted file mode 100644 index 2eb43bed..00000000 --- a/apps/api/src/game/objects/buildings/campfire.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { IGameBuildingCampfire } from '../../../../../../packages/api-sdk/src' -import { Building } from './building' - -interface ICampfireOptions { - x: number - y: number -} - -export class Campfire extends Building implements IGameBuildingCampfire { - constructor({ x, y }: ICampfireOptions) { - super({ entity: 'CAMPFIRE', x, y }) - } -} diff --git a/apps/api/src/game/objects/buildings/constructionArea.ts b/apps/api/src/game/objects/buildings/constructionArea.ts deleted file mode 100644 index 0858f988..00000000 --- a/apps/api/src/game/objects/buildings/constructionArea.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { IGameBuildingConstructionArea } from '../../../../../../packages/api-sdk/src' -import { Building } from './building' - -interface IConstructionAreaOptions { - x: number - y: number -} - -export class ConstructionArea - extends Building - implements IGameBuildingConstructionArea { - constructor({ x, y }: IConstructionAreaOptions) { - super({ entity: 'CONSTRUCTION_AREA', x, y }) - } -} diff --git a/apps/api/src/game/objects/buildings/store.ts b/apps/api/src/game/objects/buildings/store.ts deleted file mode 100644 index 43255948..00000000 --- a/apps/api/src/game/objects/buildings/store.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { IGameBuildingStore } from '../../../../../../packages/api-sdk/src' -import { Building } from './building' - -interface IStoreOptions { - x: number - y: number -} - -export class Store extends Building implements IGameBuildingStore { - constructor({ x, y }: IStoreOptions) { - super({ entity: 'STORE', x, y }) - } -} diff --git a/apps/api/src/game/objects/buildings/wagonStop.ts b/apps/api/src/game/objects/buildings/wagonStop.ts deleted file mode 100644 index d47ab92d..00000000 --- a/apps/api/src/game/objects/buildings/wagonStop.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { IGameBuildingWagonStop } from '../../../../../../packages/api-sdk/src' -import { Building } from './building' - -interface IWagonStopOptions { - x: number - y: number -} - -export class WagonStop extends Building implements IGameBuildingWagonStop { - constructor({ x, y }: IWagonStopOptions) { - super({ entity: 'WAGON_STOP', x, y }) - } -} diff --git a/apps/api/src/game/objects/buildings/warehouse.ts b/apps/api/src/game/objects/buildings/warehouse.ts deleted file mode 100644 index 18a93691..00000000 --- a/apps/api/src/game/objects/buildings/warehouse.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { IGameBuildingWarehouse } from '../../../../../../packages/api-sdk/src' -import { Building } from './building' - -interface IWarehouseOptions { - x: number - y: number -} - -export class Warehouse extends Building implements IGameBuildingWarehouse { - constructor({ x, y }: IWarehouseOptions) { - super({ entity: 'WAREHOUSE', x, y }) - } -} diff --git a/apps/api/src/game/objects/flag.ts b/apps/api/src/game/objects/flag.ts deleted file mode 100644 index 3fedde1a..00000000 --- a/apps/api/src/game/objects/flag.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectFlag, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { MAX_X, MAX_Y, MIN_X, MIN_Y } from '../../config' -import { GameObject } from './gameObject' - -interface IFlagOptions { - x?: number - y?: number - id?: string - type: IGameObjectFlag['type'] - offsetX?: number - offsetY?: number -} - -export class Flag extends GameObject implements IGameObjectFlag { - public type: IGameObjectFlag['type'] - - public isReserved: boolean - public offsetX: number - public offsetY: number - - constructor({ x, y, id, type, offsetX, offsetY }: IFlagOptions) { - const finalId = id ?? createId() - const finalX = x ?? getRandomInRange(MIN_X, MAX_X) - const finalY = y ?? getRandomInRange(MIN_Y, MAX_Y) - - super({ id: finalId, x: finalX, y: finalY, entity: 'FLAG' }) - - this.type = type - this.isReserved = false - this.offsetX = offsetX ?? 0 - this.offsetY = offsetY ?? 0 - } - - live() { - if (this.target && this.target.state === 'DESTROYED') { - this.removeTarget() - } - - const random = getRandomInRange(1, 60) - if (random <= 1) { - this.handleChange() - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } - - removeTarget() { - this.target = undefined - } -} diff --git a/apps/api/src/game/objects/gameObject.ts b/apps/api/src/game/objects/gameObject.ts deleted file mode 100644 index 164d4fce..00000000 --- a/apps/api/src/game/objects/gameObject.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { - IGameObject, - IGameObjectDirection, - IGameObjectState, - IGameScript, -} from '../../../../../packages/api-sdk/src' -import { sendMessage } from '../../websocket/websocket.server' - -interface IGameObjectOptions { - id: string - x: number - y: number - entity: IGameObject['entity'] - isVisibleOnClient?: boolean -} - -export class GameObject implements IGameObject { - public id: string - public x: number - public y: number - public health = 100 - public speedPerSecond = 1 - public size = 1 - public isVisibleOnClient: boolean - public entity: IGameObject['entity'] - public direction: IGameObjectDirection = 'RIGHT' - public state: IGameObjectState = 'IDLE' - public target: IGameObject['target'] - - public script: IGameScript | undefined - public minDistance = 1 - public needToSendDataToClient: boolean - public isOnWagonPath = false - - constructor({ id, x, y, entity, isVisibleOnClient }: IGameObjectOptions) { - this.id = id - this.x = x - this.y = y - this.entity = entity - this.isVisibleOnClient = isVisibleOnClient ?? false - - this.needToSendDataToClient = false - } - - live(): void {} - - move() { - const isOnTarget = this.checkIfIsOnTarget() - if (isOnTarget) { - this.stop() - return false - } - - if (!this.target || !this.target.x || !this.target.y) { - this.stop() - return false - } - - const distanceToX = this.getDistanceToTargetX() - const distanceToY = this.getDistanceToTargetY() - - // Fix diagonal speed - const finalSpeed - = distanceToX > 0 && distanceToY > 0 - ? this.speedPerSecond * 0.75 - : this.speedPerSecond - - this.moveX(finalSpeed > distanceToX ? distanceToX : finalSpeed) - this.moveY(finalSpeed > distanceToY ? distanceToY : finalSpeed) - return true - } - - moveX(speed: number) { - if (!this.target?.x || this.target.x === this.x) { - return - } - - if (this.x < this.target.x) { - this.direction = 'RIGHT' - this.x += speed - } - if (this.x > this.target.x) { - this.x -= speed - this.direction = 'LEFT' - } - } - - moveY(speed: number) { - if (!this.target?.y || this.target.y === this.y) { - return - } - - if (this.y < this.target.y) { - this.y += speed - } - if (this.y > this.target.y) { - this.y -= speed - } - } - - stop() { - this.state = 'IDLE' - } - - checkIfIsOnTarget() { - return ( - this.getDistanceToTargetX() + this.getDistanceToTargetY() - <= this.minDistance - ) - } - - getDistanceToTargetX() { - if (!this.target?.x) { - return 0 - } - return Math.abs(this.target.x - this.x) - } - - getDistanceToTargetY() { - if (!this.target?.y) { - return 0 - } - return Math.abs(this.target.y - this.y) - } - - public setTarget(target: IGameObject) { - this.target = target - this.state = 'MOVING' - } - - public sendMessageObjectUpdated(object: Partial = this) { - if (!this.needToSendDataToClient) { - return - } - sendMessage('OBJECT_UPDATED', object) - } -} diff --git a/apps/api/src/game/objects/index.ts b/apps/api/src/game/objects/index.ts deleted file mode 100644 index 7f19d17c..00000000 --- a/apps/api/src/game/objects/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { GameObject } from './gameObject' -export { Rabbit } from './rabbit' -export { Wolf } from './wolf' -export { Tree } from './tree' -export { Stone } from './stone' -export { Flag } from './flag' -export { Wagon } from './wagon' -export { Lake } from './lake' -export { Water } from './water' -export { Area } from './area' diff --git a/apps/api/src/game/objects/lake.ts b/apps/api/src/game/objects/lake.ts deleted file mode 100644 index ca0b99fb..00000000 --- a/apps/api/src/game/objects/lake.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectLake, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { GameObject } from './gameObject' -import { Water } from './water' - -interface ILakeOptions { - x: number - y: number - id?: string -} - -export class Lake extends GameObject implements IGameObjectLake { - public water: Water[] = [] - - constructor({ id, x, y }: ILakeOptions) { - const objectId = id ?? createId() - - super({ id: objectId, x, y, entity: 'LAKE' }) - - this.generate(13) - } - - generate(r: number) { - for (let y = r; y >= -r; --y) { - for (let x = -r; x <= r; x++) { - const value = x ** 2 + y ** 2 - - if (value < r ** 2) { - this.draw(x, y) - } - } - } - } - - draw(x: number, y: number) { - const water = new Water({ x: x * 32, y: y * 32 }) - this.water.push(water) - } - - init(width: number, height: number) { - const gridX = Math.ceil(width / 32) - const gridY = Math.floor(height / 32) - - // const center = { x: Math.round(width / 2), y: Math.round(height / 2) } - - for (let i = 0; i < gridX; i++) { - for (let j = 0; j < gridY; j++) { - const x = i * 32 - const y = j * 32 - - // if (x <= center.x && y <= center.y) { - // continue - // } - - const water = new Water({ x, y }) - this.water.push(water) - } - } - } - - live() { - if (this.state === 'IDLE') { - const random = getRandomInRange(1, 600) - if (random <= 1) { - this.handleChange() - } - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } -} diff --git a/apps/api/src/game/objects/rabbit.ts b/apps/api/src/game/objects/rabbit.ts deleted file mode 100644 index 963413f2..00000000 --- a/apps/api/src/game/objects/rabbit.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectRabbit, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { MAX_X, MAX_Y, MIN_X, MIN_Y } from '../../config' -import { GameObject } from './gameObject' - -export class Rabbit extends GameObject implements IGameObjectRabbit { - constructor() { - const id = createId() - const x = getRandomInRange(MIN_X, MAX_X) - const y = getRandomInRange(MIN_Y, MAX_Y) - - super({ id, x, y, entity: 'RABBIT' }) - - this.speed = 0.5 - this.minDistance = 5 - } - - live() { - if (this.state === 'IDLE') { - return - } - - if (this.state === 'MOVING') { - const isMoving = this.move() - this.handleChange() - - if (!isMoving) { - this.state = 'IDLE' - } - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } -} diff --git a/apps/api/src/game/objects/stone.ts b/apps/api/src/game/objects/stone.ts deleted file mode 100644 index 04236a40..00000000 --- a/apps/api/src/game/objects/stone.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectStone, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { GameObject } from './gameObject' - -interface IStoneOptions { - id?: string - x: number - y: number - resource?: number - size?: number - health?: number -} - -export class Stone extends GameObject implements IGameObjectStone { - public type: IGameObjectStone['type'] = '1' - public resource = 0 - public size = 100 - public health = 100 - public isReserved = false - - constructor({ id, x, y, resource }: IStoneOptions) { - const objectId = id ?? createId() - - super({ id: objectId, x, y, entity: 'STONE' }) - - this.state = 'IDLE' - this.resource = resource ?? getRandomInRange(1, 5) - } - - live() { - if (this.state === 'IDLE') { - const random = getRandomInRange(1, 60) - if (random <= 1) { - this.handleChange() - } - return - } - - if (this.state === 'MINING') { - if (this.health <= 0) { - this.setAsMined() - } - - const random = getRandomInRange(1, 20) - if (random <= 1 && this.health > 0) { - this.state = 'IDLE' - this.isReserved = false - this.handleChange() - } - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } - - mine() { - this.state = 'MINING' - this.isReserved = true - this.health -= 0.08 - this.handleChange() - } - - setAsMined() { - this.size = 0 - this.health = 0 - this.state = 'DESTROYED' - this.handleChange() - } -} diff --git a/apps/api/src/game/objects/tree.ts b/apps/api/src/game/objects/tree.ts deleted file mode 100644 index ad3f82ba..00000000 --- a/apps/api/src/game/objects/tree.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectTree, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { GameObject } from './gameObject' - -interface ITreeOptions { - id?: string - x: number - y: number - resource?: number - size?: number - health?: number - growSpeed?: number - type?: IGameObjectTree['type'] - variant?: IGameObjectTree['variant'] -} - -export class Tree extends GameObject implements IGameObjectTree { - public type: IGameObjectTree['type'] = '1' - public variant: IGameObjectTree['variant'] = 'GREEN' - public resource = 0 - public minSizeToChop = 75 - public maxSize = 100 - public growSpeed - public health - public isReadyToChop = false - public isReserved = false - - constructor({ - id, - x, - y, - resource, - size, - health, - growSpeed, - variant, - type, - }: ITreeOptions) { - const objectId = id ?? createId() - - super({ id: objectId, x, y, entity: 'TREE' }) - - this.state = 'IDLE' - this.resource = resource ?? getRandomInRange(1, 5) - this.size = size ?? 100 - this.health = health ?? 100 - this.growSpeed = growSpeed ?? 0.01 - this.type = type ?? this.getNewType() - this.variant = variant ?? this.getNewVariant() - } - - live() { - this.checkHealth() - - switch (this.state) { - case 'IDLE': - this.handleIdleState() - break - case 'CHOPPING': - this.handleChoppingState() - break - case 'DESTROYED': - break - } - } - - checkHealth() { - if (this.health <= 0) { - this.destroy() - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } - - handleIdleState() { - this.grow() - - const random = getRandomInRange(1, 60) - if (random <= 1) { - this.handleChange() - } - } - - handleChoppingState() { - const random = getRandomInRange(1, 20) - if (random <= 1) { - this.state = 'IDLE' - this.isReserved = false - } - } - - grow() { - if (this.size >= this.minSizeToChop && !this.isReadyToChop) { - this.isReadyToChop = true - } - if (this.size >= this.maxSize) { - return - } - - this.size += this.growSpeed - this.handleChange() - } - - public chop() { - this.state = 'CHOPPING' - this.isReserved = true - this.health -= 0.08 - this.handleChange() - } - - destroy() { - this.size = 0 - this.health = 0 - this.state = 'DESTROYED' - this.handleChange() - } - - getNewType(): IGameObjectTree['type'] { - const types: IGameObjectTree['type'][] = ['1', '2', '3', '4', '5'] - const index = getRandomInRange(0, types.length - 1) - return types[index] - } - - getNewVariant(): IGameObjectTree['variant'] { - const variants: IGameObjectTree['variant'][] = [ - 'GREEN', - 'BLUE', - 'STONE', - 'TEAL', - 'TOXIC', - 'VIOLET', - ] - const index = getRandomInRange(0, variants.length - 1) - return variants[index] - } -} diff --git a/apps/api/src/game/objects/units/index.ts b/apps/api/src/game/objects/units/index.ts deleted file mode 100644 index a546bafe..00000000 --- a/apps/api/src/game/objects/units/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { Mechanic } from './mechanic' -export { Player } from './player' -export { Raider } from './raider' -export { Unit } from './unit' -export { VillageCourier } from './villageCourier' -export { VillageFarmer } from './villageFarmer' diff --git a/apps/api/src/game/objects/units/mechanic.ts b/apps/api/src/game/objects/units/mechanic.ts deleted file mode 100644 index a942e67c..00000000 --- a/apps/api/src/game/objects/units/mechanic.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectMechanic } from '../../../../../../packages/api-sdk/src' -import { Unit } from './unit' - -interface IMechanicOptions { - x: number - y: number -} - -export class Mechanic extends Unit implements IGameObjectMechanic { - constructor({ x, y }: IMechanicOptions) { - const id = createId() - - super({ - id, - x, - y, - entity: 'MECHANIC', - visual: { - head: '1', - hairstyle: 'COAL_LONG', - top: 'DARK_SILVER_SHIRT', - }, - }) - - this.userName = 'Mechanic' - } - - live() { - super.live() - this.handleChange() - } - - handleChange() { - const prepared = { - ...this, - script: undefined, - live: undefined, - } - - this.sendMessageObjectUpdated(prepared) - } -} diff --git a/apps/api/src/game/objects/units/player.ts b/apps/api/src/game/objects/units/player.ts deleted file mode 100644 index 8b8bb758..00000000 --- a/apps/api/src/game/objects/units/player.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectPlayer, - type IGameSkill, - getRandomInRange, -} from '../../../../../../packages/api-sdk/src' -import { db } from '../../../db/db.client' -import { Inventory, Skill } from '../../common' -import { Unit } from './unit' - -interface IPlayerOptions { - id?: string - x: number - y: number -} - -export class Player extends Unit implements IGameObjectPlayer { - public coins = 0 - public reputation = 0 - public villainPoints = 0 - public refuellerPoints = 0 - public raiderPoints = 0 - public lastActionAt: IGameObjectPlayer['lastActionAt'] = new Date() - public health = 100 - - public inventoryId?: string - - public skills: Skill[] = [] - - constructor({ id, x, y }: IPlayerOptions) { - const objectId = id ?? createId() - - super({ id: objectId, x, y, entity: 'PLAYER' }) - - this.speedPerSecond = 2 - } - - async init() { - await this.readFromDB() - await this.initSkillsFromDB() - super.initVisual({ - head: '1', - hairstyle: 'CLASSIC', - top: 'VIOLET_SHIRT', - }) - } - - live() { - super.live() - this.handleChange() - } - - handleChange() { - const prepared = { - ...this, - script: undefined, - live: undefined, - } - - this.sendMessageObjectUpdated(prepared) - } - - async chopTree() { - super.chopTree() - - await this.findOrCreateSkillInDB('WOODSMAN') - this.upSkill('WOODSMAN') - this.handleChange() - } - - async mineStone() { - super.mineStone() - - await this.findOrCreateSkillInDB('MINER') - this.upSkill('MINER') - this.handleChange() - } - - updateCoins(amount: number) { - this.coins = this.coins + amount - this.handleChange() - - return db.player.update({ - where: { id: this.id }, - data: { - coins: this.coins, - }, - }) - } - - addReputation(amount: number) { - this.reputation += amount - this.handleChange() - - return db.player.update({ - where: { id: this.id }, - data: { - reputation: this.reputation, - }, - }) - } - - addRefuellerPoints(amount: number) { - if (amount < 0) { - return - } - - this.refuellerPoints += amount - this.handleChange() - - return db.player.update({ - where: { id: this.id }, - data: { - refuellerPoints: this.refuellerPoints, - }, - }) - } - - addVillainPoints(amount: number) { - this.villainPoints += amount - this.handleChange() - - return db.player.update({ - where: { id: this.id }, - data: { - villainPoints: { - increment: amount, - }, - }, - }) - } - - addRaiderPoints(amount: number) { - this.raiderPoints += amount - this.handleChange() - - return db.player.update({ - where: { id: this.id }, - data: { - raiderPoints: { - increment: amount, - }, - }, - }) - } - - public async readFromDB() { - const player = await db.player.findUnique({ where: { id: this.id } }) - if (!player) { - return - } - - this.userName = player.userName - this.coins = player.coins - this.reputation = player.reputation - this.villainPoints = player.villainPoints - this.refuellerPoints = player.refuellerPoints - this.raiderPoints = player.raiderPoints - this.inventoryId = player.inventoryId - } - - public updateLastActionAt() { - this.lastActionAt = new Date() - return db.player.update({ - where: { id: this.id }, - data: { - lastActionAt: new Date(), - }, - }) - } - - public async initInventoryFromDB() { - if (!this.inventoryId) { - return - } - - const inventory = new Inventory({ - objectId: this.id, - id: this.inventoryId, - saveInDb: true, - }) - await inventory.init() - this.inventory = inventory - } - - public async initSkillsFromDB() { - this.skills = [] - const skills = await Skill.findAllInDB(this.id) - for (const skill of skills) { - const instance = new Skill({ id: skill.id }) - await instance.init() - this.skills.push(instance) - } - } - - async findOrCreateSkillInDB(type: IGameSkill['type']) { - const skill = this.skills.find((skill) => skill.type === type) - if (!skill) { - await Skill.createInDB(this.id, type) - await this.initSkillsFromDB() - return this.skills.find((skill) => skill.type === type) as Skill - } - - return skill - } - - public upSkill(type: IGameSkill['type']) { - const random = getRandomInRange(1, 200) - if (random <= 1) { - const skill = this.skills.find((skill) => skill.type === type) - if (skill) { - void skill.addXp() - } - } - } -} diff --git a/apps/api/src/game/objects/units/raider.ts b/apps/api/src/game/objects/units/raider.ts deleted file mode 100644 index 7ed7fc8a..00000000 --- a/apps/api/src/game/objects/units/raider.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectRaider } from '../../../../../../packages/api-sdk/src' -import { Unit } from './unit' - -interface IRaiderOptions { - x: number - y: number -} - -export class Raider extends Unit implements IGameObjectRaider { - constructor({ x, y }: IRaiderOptions) { - const objectId = createId() - - super({ - id: objectId, - x, - y, - entity: 'RAIDER', - visual: { - head: '1', - hairstyle: 'BOLD', - top: 'BLACK_SHIRT', - }, - }) - - this.speedPerSecond = 1.5 - this.userName = 'Raider' - } - - live() { - super.live() - this.handleChange() - } - - handleChange() { - const prepared = { - ...this, - script: undefined, - live: undefined, - } - - this.sendMessageObjectUpdated(prepared) - } -} diff --git a/apps/api/src/game/objects/units/trader.ts b/apps/api/src/game/objects/units/trader.ts deleted file mode 100644 index e6d30b77..00000000 --- a/apps/api/src/game/objects/units/trader.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectTrader } from '../../../../../../packages/api-sdk/src' -import { generateUnitUserName } from '../../common/generators/unitName' -import { generateUnitTop } from '../../common/generators/unitTop' -import { Unit } from './unit' - -interface ITraderOptions { - x: number - y: number -} - -export class Trader extends Unit implements IGameObjectTrader { - constructor({ x, y }: ITraderOptions) { - const id = createId() - - super({ - id, - x, - y, - entity: 'TRADER', - visual: { - head: '1', - hairstyle: 'COAL_LONG', - top: generateUnitTop(), - }, - }) - - this.speedPerSecond = 1 - this.minDistance = 5 - this.userName = generateUnitUserName() - } - - live() { - super.live() - this.handleChange() - } - - handleChange() { - const prepared = { - ...this, - script: undefined, - live: undefined, - } - - this.sendMessageObjectUpdated(prepared) - } -} diff --git a/apps/api/src/game/objects/units/unit.ts b/apps/api/src/game/objects/units/unit.ts deleted file mode 100644 index 47c6d596..00000000 --- a/apps/api/src/game/objects/units/unit.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObject, - type IGameObjectUnit, - getRandomInRange, -} from '../../../../../../packages/api-sdk/src' -import { Inventory } from '../../common' -import { GameObject } from '../gameObject' -import { Stone } from '../stone' -import { Tree } from '../tree' - -interface IUnitOptions { - entity: IGameObject['entity'] - id?: string - x: number - y: number - visual?: IGameObjectUnit['visual'] -} - -export class Unit extends GameObject implements IGameObjectUnit { - public userName!: IGameObjectUnit['userName'] - public inventory!: Inventory - public visual!: IGameObjectUnit['visual'] - public coins: IGameObjectUnit['coins'] - public dialogue!: IGameObjectUnit['dialogue'] - - constructor({ entity, id, x, y, visual }: IUnitOptions) { - const objectId = id ?? createId() - - super({ id: objectId, x, y, entity }) - - this.initInventory() - this.initVisual(visual) - this.initDialogue() - this.coins = 0 - } - - live() { - this.handleMessages() - - if (this.script) { - return this.script.live() - } - } - - public initInventory() { - this.inventory = new Inventory({ - objectId: this.id, - id: createId(), - saveInDb: false, - }) - } - - public initVisual(visual: IGameObjectUnit['visual'] | undefined) { - this.visual = visual ?? { - head: '1', - hairstyle: 'CLASSIC', - top: 'VIOLET_SHIRT', - } - } - - initDialogue() { - this.dialogue = { - messages: [], - } - } - - public addMessage(message: string) { - const MAX_CHARS = 100 - const messagePrepared - = message.trim().slice(0, MAX_CHARS) - + (message.length > MAX_CHARS ? '...' : '') - - this.dialogue.messages.push({ - id: createId(), - text: messagePrepared, - }) - } - - public handleMessages() { - const random = getRandomInRange(1, 200) - if (random === 1) { - this.dialogue.messages.splice(0, 1) - } - } - - public chopTree() { - if (this.target instanceof Tree && this.target.state !== 'DESTROYED') { - this.direction = 'RIGHT' - this.state = 'CHOPPING' - this.checkAndBreakTool('AXE') - - this.target.chop() - } - } - - public mineStone() { - if (this.target instanceof Stone && this.target.state !== 'DESTROYED') { - this.direction = 'RIGHT' - this.state = 'MINING' - this.checkAndBreakTool('PICKAXE') - - this.target.mine() - } - } - - checkAndBreakTool(type: 'AXE' | 'PICKAXE') { - const tool = this.inventory.items.find((item) => item.type === type) - if (tool) { - // this.target.health -= 0.16 - const random = getRandomInRange(1, 40) - if (random <= 1) { - void this.inventory.checkAndBreakItem(tool, 1) - } - } - } -} diff --git a/apps/api/src/game/objects/units/villageCourier.ts b/apps/api/src/game/objects/units/villageCourier.ts deleted file mode 100644 index 029c3e3e..00000000 --- a/apps/api/src/game/objects/units/villageCourier.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectCourier } from '../../../../../../packages/api-sdk/src' -import { generateUnitUserName } from '../../common/generators/unitName' -import { generateUnitTop } from '../../common/generators/unitTop' -import { Unit } from './unit' - -interface ICourierOptions { - x: number - y: number -} - -export class VillageCourier extends Unit implements IGameObjectCourier { - constructor({ x, y }: ICourierOptions) { - const id = createId() - - super({ - id, - x, - y, - entity: 'COURIER', - visual: { - head: '1', - hairstyle: 'BOLD', - top: generateUnitTop(), - }, - }) - - this.speed = 2.5 - this.minDistance = 15 - this.userName = generateUnitUserName() - } - - live() { - super.live() - this.handleChange() - } - - handleChange() { - const prepared = { - ...this, - script: undefined, - live: undefined, - } - - this.sendMessageObjectUpdated(prepared) - } -} diff --git a/apps/api/src/game/objects/units/villageFarmer.ts b/apps/api/src/game/objects/units/villageFarmer.ts deleted file mode 100644 index 8453bb35..00000000 --- a/apps/api/src/game/objects/units/villageFarmer.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectFarmer } from '../../../../../../packages/api-sdk/src' -import { generateUnitUserName } from '../../common/generators/unitName' -import { generateUnitTop } from '../../common/generators/unitTop' -import { Unit } from './unit' - -interface IVillageFarmerOptions { - x: number - y: number -} - -export class VillageFarmer extends Unit implements IGameObjectFarmer { - constructor({ x, y }: IVillageFarmerOptions) { - const id = createId() - - super({ - id, - x, - y, - entity: 'FARMER', - visual: { - head: '1', - hairstyle: 'ORANGE_WITH_BEARD', - top: generateUnitTop(), - }, - }) - - this.userName = generateUnitUserName() - } - - live() { - super.live() - this.handleChange() - } - - handleChange() { - const prepared = { - ...this, - script: undefined, - live: undefined, - } - - this.sendMessageObjectUpdated(prepared) - } -} diff --git a/apps/api/src/game/objects/wagon.ts b/apps/api/src/game/objects/wagon.ts deleted file mode 100644 index 27eacb16..00000000 --- a/apps/api/src/game/objects/wagon.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectWagon } from '../../../../../packages/api-sdk/src' -import { Inventory } from '../common' -import { GameObject } from './gameObject' -import { Mechanic } from './units' - -interface IWagonOptions { - x: number - y: number -} - -export class Wagon extends GameObject implements IGameObjectWagon { - public fuel: number - public visibilityArea!: IGameObjectWagon['visibilityArea'] - public cargoType: IGameObjectWagon['cargoType'] - - public cargo: Inventory | undefined - public mechanic!: Mechanic - public serverDataArea!: IGameObjectWagon['visibilityArea'] - public collisionArea!: IGameObjectWagon['visibilityArea'] - - constructor({ x, y }: IWagonOptions) { - const finalId = createId() - - super({ id: finalId, x, y, entity: 'WAGON', isVisibleOnClient: true }) - - this.needToSendDataToClient = true - this.speed = 0 - this.fuel = 2000 - this.updateVisibilityArea() - this.updateServerDataArea() - this.initMechanic() - } - - live() { - this.updateVisibilityArea() - this.updateServerDataArea() - this.updateCollisionArea() - this.updateMechanic() - this.consumeFuel() - - if (this.state === 'IDLE') { - this.handleChange() - return - } - if (this.state === 'WAITING') { - this.handleChange() - } - } - - handleChange() { - this.sendMessageObjectUpdated() - } - - consumeFuel() { - if (this.speed <= 0) { - return - } - - this.fuel -= this.speed * 2 - } - - refuel(woodAmount: number) { - if (woodAmount < 0) { - return - } - - this.fuel += woodAmount * 5 * 40 - } - - emptyFuel() { - this.fuel = 0 - } - - updateVisibilityArea() { - const offsetX = 2560 / 2 - const offsetY = 1440 / 2 - - this.visibilityArea = { - startX: this.x - offsetX, - endX: this.x + offsetX, - startY: this.y - offsetY, - endY: this.y + offsetY, - } - } - - updateServerDataArea() { - const offsetX = 2560 * 1.5 - const offsetY = 1440 - - this.serverDataArea = { - startX: this.x - offsetX, - endX: this.x + offsetX, - startY: this.y - offsetY, - endY: this.y + offsetY, - } - } - - updateCollisionArea() { - const offsetX = 250 - const offsetY = 180 - - this.collisionArea = { - startX: this.x - offsetX, - endX: this.x + offsetX, - startY: this.y - offsetY, - endY: this.y + offsetY, - } - } - - public checkIfPointInCollisionArea(point: { x: number, y: number }) { - return ( - this.collisionArea.startX < point.x - && point.x < this.collisionArea.endX - && this.collisionArea.startY < point.y - && point.y < this.collisionArea.endY - ) - } - - public checkIfPointInVisibilityArea(point: { x: number, y: number }) { - return ( - this.visibilityArea.startX < point.x - && point.x < this.visibilityArea.endX - && this.visibilityArea.startY < point.y - && point.y < this.visibilityArea.endY - ) - } - - public checkIfPointInServerDataArea(point: { x: number, y: number }) { - return ( - this.serverDataArea.startX < point.x - && point.x < this.serverDataArea.endX - && this.serverDataArea.startY < point.y - && point.y < this.serverDataArea.endY - ) - } - - initMechanic() { - this.mechanic = new Mechanic({ x: this.x, y: this.y }) - } - - updateMechanic() { - this.mechanic.isVisibleOnClient = true - this.mechanic.needToSendDataToClient = true - this.mechanic.live() - this.mechanic.direction = 'LEFT' - this.mechanic.x = this.x - 50 - this.mechanic.y = this.y - 48 - } - - public setCargo() { - this.cargo = new Inventory({ - id: createId(), - saveInDb: false, - objectId: this.id, - }) - void this.cargo.addOrCreateItem('WOOD', 100) - this.cargoType = 'CHEST' - } - - public emptyCargo() { - this.cargo = undefined - this.cargoType = undefined - } -} diff --git a/apps/api/src/game/objects/water.ts b/apps/api/src/game/objects/water.ts deleted file mode 100644 index ea5b22c5..00000000 --- a/apps/api/src/game/objects/water.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameObjectWater } from '../../../../../packages/api-sdk/src' -import { GameObject } from './gameObject' - -interface IWaterOptions { - x: number - y: number -} - -export class Water extends GameObject implements IGameObjectWater { - constructor({ x, y }: IWaterOptions) { - const id = createId() - - super({ id, x, y, entity: 'WATER' }) - } -} diff --git a/apps/api/src/game/objects/wolf.ts b/apps/api/src/game/objects/wolf.ts deleted file mode 100644 index 9aeaee82..00000000 --- a/apps/api/src/game/objects/wolf.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type IGameObjectWolf, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { MAX_X, MAX_Y, MIN_X, MIN_Y } from '../../config' -import { GameObject } from './gameObject' - -export class Wolf extends GameObject implements IGameObjectWolf { - constructor() { - const id = createId() - const x = getRandomInRange(MIN_X, MAX_X) - const y = getRandomInRange(MIN_Y, MAX_Y) - - super({ id, x, y, entity: 'WOLF' }) - } - - live() { - if (this.state === 'IDLE') { - this.sendMessageObjectUpdated() - return - } - - if (this.state === 'MOVING') { - this.move() - - this.sendMessageObjectUpdated() - } - } -} diff --git a/apps/api/src/game/quests/noTradingPostQuest.ts b/apps/api/src/game/quests/noTradingPostQuest.ts deleted file mode 100644 index 7a3280d5..00000000 --- a/apps/api/src/game/quests/noTradingPostQuest.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameQuestTaskFunc } from '../../../../../packages/api-sdk/src' -import type { DonateWoodToVillageAction } from '../actions/donateWoodToVillageAction' -import { Quest } from './quest' - -interface INoTradingPostQuestOptions { - creatorId: string - taskUpdateFunc1: IGameQuestTaskFunc - taskUpdateFunc2: IGameQuestTaskFunc - taskAction1: DonateWoodToVillageAction -} - -export class NoTradingPostQuest extends Quest { - constructor({ - creatorId, - taskUpdateFunc1, - taskUpdateFunc2, - taskAction1, - }: INoTradingPostQuestOptions) { - super({ - type: 'SIDE', - title: 'No Trading Post', - description: 'The locals need help. Traders are expected to arrive.', - }) - - this.creatorId = creatorId - this.initTasks({ taskUpdateFunc1, taskUpdateFunc2, taskAction1 }) - } - - initTasks({ - taskUpdateFunc1, - taskUpdateFunc2, - taskAction1, - }: { - taskUpdateFunc1: IGameQuestTaskFunc - taskUpdateFunc2: IGameQuestTaskFunc - taskAction1: DonateWoodToVillageAction - }) { - this.tasks = [ - { - id: createId(), - status: 'ACTIVE', - description: 'Accumulate 25 wood in the warehouse', - progressNow: 0, - progressToSuccess: 25, - updateProgress: taskUpdateFunc1, - action: taskAction1, - }, - { - id: createId(), - status: 'ACTIVE', - description: 'Build Trading Post', - progressNow: false, - progressToSuccess: true, - updateProgress: taskUpdateFunc2, - }, - ] - } -} diff --git a/apps/api/src/game/quests/quest.ts b/apps/api/src/game/quests/quest.ts deleted file mode 100644 index 644760e4..00000000 --- a/apps/api/src/game/quests/quest.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameQuest } from '../../../../../packages/api-sdk/src' - -interface IQuestOptions { - type: IGameQuest['type'] - title: IGameQuest['title'] - description: IGameQuest['description'] -} - -export class Quest implements IGameQuest { - public id: string - public type: IGameQuest['type'] - public title: IGameQuest['title'] - public description: IGameQuest['description'] - public tasks: IGameQuest['tasks'] - public status: IGameQuest['status'] - public creatorId!: IGameQuest['creatorId'] - public conditions!: IGameQuest['conditions'] - - constructor({ type, title, description }: IQuestOptions) { - this.id = createId() - this.type = type - this.title = title - this.description = description - this.tasks = [] - - this.status = 'ACTIVE' - this.conditions = {} - } -} diff --git a/apps/api/src/game/quests/treesAreRunningOutQuest.ts b/apps/api/src/game/quests/treesAreRunningOutQuest.ts deleted file mode 100644 index bbcce114..00000000 --- a/apps/api/src/game/quests/treesAreRunningOutQuest.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { IGameQuestTaskFunc } from '../../../../../packages/api-sdk/src' -import type { PlantTreeAction } from '../actions/plantTreeAction' -import { Quest } from './quest' - -interface ITreesAreRunningOutQuestOptions { - creatorId: string - taskUpdateFunc1: IGameQuestTaskFunc - taskAction1: PlantTreeAction -} - -export class TreesAreRunningOutQuest extends Quest { - constructor({ - creatorId, - taskUpdateFunc1, - taskAction1, - }: ITreesAreRunningOutQuestOptions) { - super({ - type: 'SIDE', - title: 'The trees are running out!', - description: - 'In the village, someone is actively cutting down trees. Help is needed!', - }) - - this.creatorId = creatorId - this.initTasks({ taskUpdateFunc1, taskAction1 }) - } - - initTasks({ - taskUpdateFunc1, - taskAction1, - }: { - taskUpdateFunc1: IGameQuestTaskFunc - taskAction1: PlantTreeAction - }) { - this.tasks = [ - { - id: createId(), - status: 'ACTIVE', - description: 'There should be at least 30 trees', - progressNow: 0, - progressToSuccess: 30, - updateProgress: taskUpdateFunc1, - action: taskAction1, - }, - ] - } -} diff --git a/apps/api/src/game/scenes/defenceScene.ts b/apps/api/src/game/scenes/defenceScene.ts deleted file mode 100644 index f5471826..00000000 --- a/apps/api/src/game/scenes/defenceScene.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { getRandomInRange } from '../../../../../packages/api-sdk/src' -import type { Game } from '../game' -import { Stone, Tree } from '../objects' -import { GameScene } from './gameScene' - -interface IDefenceSceneOptions { - game: Game -} - -export class DefenceScene extends GameScene { - public wood!: number - public stone!: number - - constructor({ game }: IDefenceSceneOptions) { - super({ - game, - }) - - void this.init() - } - - public async init() { - await this.initGroupPlayers() - this.initTrees(12) - this.initStones(8) - - this.wood = 0 - this.stone = 0 - - void this.play() - } - - async initGroupPlayers() { - if (!this.group) { - return - } - - for (const player of this.group.players) { - const instance = await this.initPlayer(player.id) - this.objects.push(instance) - } - } - - private initTrees(count: number) { - for (let i = 0; i < count; i++) { - const flag = this.findRandomEmptyResourceFlag() - if (flag) { - const size = getRandomInRange(75, 90) - const tree = new Tree({ x: flag.x, y: flag.y, size, resource: 1 }) - flag.target = tree - this.objects.push(tree) - } - } - } - - private initStones(count: number) { - for (let i = 0; i < count; i++) { - const flag = this.findRandomEmptyResourceFlag() - if (flag) { - const stone = new Stone({ x: flag.x, y: flag.y, resource: 1 }) - flag.target = stone - this.objects.push(stone) - } - } - } -} diff --git a/apps/api/src/game/scenes/gameScene.ts b/apps/api/src/game/scenes/gameScene.ts deleted file mode 100644 index 752dd627..00000000 --- a/apps/api/src/game/scenes/gameScene.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - type GetSceneResponse, - type IGameChunk, - type IGameChunkTheme, - type IGameInventoryItem, - getDateMinusMinutes, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { SERVER_TICK_MS } from '../../config' -import { type GameChunk, Village } from '../chunks' -import { Group, Route } from '../common' -import type { Game } from '../game' -import { - Flag, - type GameObject, - type Rabbit, - Stone, - Tree, - type Wolf, -} from '../objects' -import { Player, Raider } from '../objects/units' -import { Trader } from '../objects/units/trader' -import { ChopTreeScript } from '../scripts/chopTreeScript' -import { MoveOffScreenAndSelfDestroyScript } from '../scripts/moveOffScreenAndSelfDestroyScript' -import { MoveToTargetScript } from '../scripts/moveToTargetScript' -import { ActionService } from '../services/actionService' -import { EventService } from '../services/eventService' -import { TradeService } from '../services/tradeService' -import { WagonService } from '../services/wagonService' - -interface IGameSceneOptions { - game: Game -} - -export class GameScene { - public id: string - public game: Game - public objects: GameObject[] = [] - public group: Group - public chunks: GameChunk[] = [] - public chunkNow: GameChunk | undefined - - public actionService: ActionService - public eventService: EventService - public tradeService: TradeService - public wagonService: WagonService - - constructor({ game }: IGameSceneOptions) { - this.id = createId() - this.game = game - 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 }) - } - - public async play() { - return setInterval(() => { - this.eventService.update() - this.tradeService.update() - this.wagonService.update() - this.updateObjects() - this.updateChunks() - this.updateChunkNow() - }, SERVER_TICK_MS) - } - - destroy() { - this.objects = [] - } - - 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, - isVisibleOnClient: this.chunkNow.isVisibleOnClient, - } - } - - 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.wagonService.routeService.getRoute(), - warehouseItems: this.getWarehouseItems(), - } - } - - updateObjects() { - this.removeInactivePlayers() - const wagon = this.wagonService.wagon - - for (const obj of this.objects) { - this.removeDestroyedObject(obj) - - obj.isVisibleOnClient = wagon.checkIfPointInVisibilityArea({ - x: obj.x, - y: obj.y, - }) - obj.needToSendDataToClient = wagon.checkIfPointInServerDataArea({ - x: obj.x, - y: obj.y, - }) - - 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() { - const wagon = this.wagonService.wagon - - for (const chunk of this.chunks) { - for (const object of chunk.objects) { - if (object.state === 'DESTROYED') { - chunk.removeObject(object) - } - } - - chunk.isVisibleOnClient = wagon.checkIfPointInVisibilityArea({ - x: chunk.center.x, - y: chunk.center.y, - }) - chunk.needToSendDataToClient = wagon.checkIfPointInServerDataArea({ - x: chunk.center.x, - y: chunk.center.y, - }) - - chunk.live() - } - } - - updateChunkNow() { - this.chunkNow = undefined - - const wagon = this.wagonService.wagon - - for (const chunk of this.chunks) { - const isWagonOnThisChunk = chunk.checkIfPointIsInArea({ - x: wagon.x, - y: 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({ id, x: -100, y: -100 }) - await instance.init() - 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({ 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/apps/api/src/game/scenes/index.ts b/apps/api/src/game/scenes/index.ts deleted file mode 100644 index 36c48f2b..00000000 --- a/apps/api/src/game/scenes/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { GameScene } from './gameScene' -export { VillageScene } from './villageScene' -export { DefenceScene } from './defenceScene' -export { MovingScene } from './movingScene' diff --git a/apps/api/src/game/scenes/movingScene.ts b/apps/api/src/game/scenes/movingScene.ts deleted file mode 100644 index a7462cf5..00000000 --- a/apps/api/src/game/scenes/movingScene.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { Game } from '../game' -import { GameScene } from './gameScene' - -interface IMovingSceneOptions { - game: Game -} - -export class MovingScene extends GameScene { - constructor({ game }: IMovingSceneOptions) { - super({ - game, - }) - - void this.init() - } - - public async init() { - const village = this.initStartingVillage() - const wagonStartPoint = village.getWagonStopPoint() - - this.wagonService.initWagon(wagonStartPoint) - await this.initGroupPlayers() - - void this.play() - } - - initStartingVillage() { - const initialOffsetX = 500 - const initialOffsetY = 2000 - const width = 3600 - const height = 2000 - const area = { - width, - height, - center: { - x: Math.round(width / 2 + initialOffsetX), - y: Math.round(height / 2 + initialOffsetY), - }, - } - const village = this.wagonService.routeService.generateRandomVillage({ - center: area.center, - width: area.width, - height: area.height, - theme: this.getRandomTheme(), - }) - this.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/apps/api/src/game/scenes/villageScene.ts b/apps/api/src/game/scenes/villageScene.ts deleted file mode 100644 index e3946d65..00000000 --- a/apps/api/src/game/scenes/villageScene.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Game } from '../game' -import { Rabbit, Wolf } from '../objects' -import { GameScene } from './gameScene' - -interface IVillageSceneOptions { - game: Game -} - -export class VillageScene extends GameScene { - constructor({ game }: IVillageSceneOptions) { - super({ - game, - }) - - void this.init() - } - - public async init() { - await this.initGroupPlayers() - this.initRabbits(8) - this.initWolfs(4) - - void this.play() - } - - async initGroupPlayers() { - if (!this.group) { - return - } - - for (const player of this.group.players) { - const instance = await this.initPlayer(player.id) - this.objects.push(instance) - } - } - - private initRabbits(count: number) { - for (let i = 0; i < count; i++) { - this.objects.push(new Rabbit()) - } - } - - private initWolfs(count: number) { - for (let i = 0; i < count; i++) { - this.objects.push(new Wolf()) - } - } -} diff --git a/apps/api/src/game/scripts/buildScript.ts b/apps/api/src/game/scripts/buildScript.ts deleted file mode 100644 index b61a8467..00000000 --- a/apps/api/src/game/scripts/buildScript.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IBuildScriptOptions { - object: GameObject - target: IGameObject - buildFunc: () => boolean -} - -export class BuildScript extends Script { - constructor({ target, object, buildFunc }: IBuildScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.build(buildFunc), - ] - } - - build(func: () => boolean): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - const isFinished = func() - if (isFinished) { - this.markTaskAsDone() - } - }, - } - } -} diff --git a/apps/api/src/game/scripts/chopTreeScript.ts b/apps/api/src/game/scripts/chopTreeScript.ts deleted file mode 100644 index f599aa80..00000000 --- a/apps/api/src/game/scripts/chopTreeScript.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IPlantNewTreeScriptOptions { - object: GameObject - target: IGameObject - chopTreeFunc: () => boolean -} - -export class ChopTreeScript extends Script { - constructor({ target, object, chopTreeFunc }: IPlantNewTreeScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.chopTree(chopTreeFunc), - ] - } - - chopTree(func: () => boolean): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - const isFinished = func() - if (isFinished) { - this.markTaskAsDone() - } - }, - } - } -} diff --git a/apps/api/src/game/scripts/mineStoneScript.ts b/apps/api/src/game/scripts/mineStoneScript.ts deleted file mode 100644 index 4f3dd274..00000000 --- a/apps/api/src/game/scripts/mineStoneScript.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IMineStoneScriptOptions { - object: GameObject - target: IGameObject - mineStoneFunc: () => boolean -} - -export class MineStoneScript extends Script { - constructor({ target, object, mineStoneFunc }: IMineStoneScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.mineStone(mineStoneFunc), - ] - } - - mineStone(func: () => boolean): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - const isFinished = func() - if (isFinished) { - this.markTaskAsDone() - } - }, - } - } -} diff --git a/apps/api/src/game/scripts/moveOffScreenAndSelfDestroyScript.ts b/apps/api/src/game/scripts/moveOffScreenAndSelfDestroyScript.ts deleted file mode 100644 index f09fa01d..00000000 --- a/apps/api/src/game/scripts/moveOffScreenAndSelfDestroyScript.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IMoveOffScreenAndSelfDestroyScriptOptions { - object: GameObject - target: IGameObject - selfDestroyFunc: () => void -} - -export class MoveOffScreenAndSelfDestroyScript extends Script { - constructor({ - target, - object, - selfDestroyFunc, - }: IMoveOffScreenAndSelfDestroyScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.selfDestroy(selfDestroyFunc), - ] - } - - selfDestroy(func: () => void): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - func() - this.markTaskAsDone() - }, - } - } -} diff --git a/apps/api/src/game/scripts/moveToTargetScript.ts b/apps/api/src/game/scripts/moveToTargetScript.ts deleted file mode 100644 index d9c61234..00000000 --- a/apps/api/src/game/scripts/moveToTargetScript.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { IGameObject } from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IMoveToRandomTargetScriptOptions { - object: GameObject - target: IGameObject -} - -export class MoveToTargetScript extends Script { - constructor({ target, object }: IMoveToRandomTargetScriptOptions) { - super({ object }) - - this.tasks = [this.setTarget(target), this.runToTarget()] - this.isInterruptible = true - } -} diff --git a/apps/api/src/game/scripts/moveToTradePostAndTradeScript.ts b/apps/api/src/game/scripts/moveToTradePostAndTradeScript.ts deleted file mode 100644 index 277caecb..00000000 --- a/apps/api/src/game/scripts/moveToTradePostAndTradeScript.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IMoveToTradePostAndTradeScriptOptions { - object: GameObject - target: IGameObject - startTradeFunc: () => void -} - -export class MoveToTradePostAndTradeScript extends Script { - constructor({ - target, - object, - startTradeFunc, - }: IMoveToTradePostAndTradeScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.startTrade(startTradeFunc), - ] - } - - startTrade(func: () => void): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - func() - this.markTaskAsDone() - }, - } - } -} diff --git a/apps/api/src/game/scripts/placeItemInWarehouseScript.ts b/apps/api/src/game/scripts/placeItemInWarehouseScript.ts deleted file mode 100644 index 391f7066..00000000 --- a/apps/api/src/game/scripts/placeItemInWarehouseScript.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IPlaceItemInWarehouseScriptOptions { - object: GameObject - target: IGameObject - placeItemFunc: () => void -} - -export class PlaceItemInWarehouseScript extends Script { - constructor({ - target, - object, - placeItemFunc, - }: IPlaceItemInWarehouseScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.placeItem(placeItemFunc), - ] - } - - placeItem(func: () => void): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - func() - this.markTaskAsDone() - }, - } - } -} diff --git a/apps/api/src/game/scripts/plantNewTreeScript.ts b/apps/api/src/game/scripts/plantNewTreeScript.ts deleted file mode 100644 index 2a3c0b58..00000000 --- a/apps/api/src/game/scripts/plantNewTreeScript.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - IGameObject, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' -import { Script } from './script' - -interface IPlantNewTreeScriptOptions { - object: GameObject - target: IGameObject - plantNewTreeFunc: () => void -} - -export class PlantNewTreeScript extends Script { - constructor({ - target, - object, - plantNewTreeFunc, - }: IPlantNewTreeScriptOptions) { - super({ object }) - - this.tasks = [ - this.setTarget(target), - this.runToTarget(), - this.plantNewTree(plantNewTreeFunc), - ] - } - - plantNewTree(func: () => void): IGameTask { - return { - id: '3', - status: 'IDLE', - live: () => { - func() - this.markTaskAsDone() - }, - } - } -} diff --git a/apps/api/src/game/scripts/script.ts b/apps/api/src/game/scripts/script.ts deleted file mode 100644 index f9846aa5..00000000 --- a/apps/api/src/game/scripts/script.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { - IGameObject, - IGameScript, - IGameTask, -} from '../../../../../packages/api-sdk/src' -import type { GameObject } from '../objects' - -interface IScriptOptions { - object: GameObject -} - -export class Script implements IGameScript { - public id: string - public tasks!: IGameScript['tasks'] - public isInterruptible = false - - public object!: GameObject - - constructor({ object }: IScriptOptions) { - this.id = createId() - this.object = object - } - - live() { - const activeTask = this.getActiveTask() - if (!activeTask) { - const nextTask = this.getNextIdleTask() - if (!nextTask) { - return this.markScriptAsFinished() - } - - return this.markTaskAsActive(nextTask) - } - - return activeTask.live() - } - - getActiveTask() { - return this.tasks.find((t) => t.status === 'ACTIVE') - } - - getNextIdleTask() { - return this.tasks.find((t) => t.status === 'IDLE') - } - - markTaskAsActive(task: IGameTask) { - task.status = 'ACTIVE' - } - - markTaskAsDone() { - const activeTask = this.getActiveTask() - if (!activeTask) { - return - } - - activeTask.status = 'DONE' - } - - markScriptAsFinished() { - this.object.script = undefined - } - - setTarget(target: IGameObject): IGameTask { - return { - id: createId(), - status: 'IDLE', - live: () => { - this.object.target = target - this.object.state = 'MOVING' - this.markTaskAsDone() - }, - } - } - - runToTarget(): IGameTask { - return { - id: createId(), - status: 'IDLE', - live: () => { - const isMoving = this.object.move() - if (!isMoving) { - this.markTaskAsDone() - } - }, - } - } -} diff --git a/apps/api/src/game/services/actionService.ts b/apps/api/src/game/services/actionService.ts deleted file mode 100644 index 8c4e2940..00000000 --- a/apps/api/src/game/services/actionService.ts +++ /dev/null @@ -1,684 +0,0 @@ -import type { - GameSceneType, - IGameActionResponse, - IGameSceneAction, - ItemType, -} from '../../../../../packages/api-sdk/src' -import { ANSWER } from '../../../../../packages/api-sdk/src/lib/actionAnswer' -import { - ADMIN_PLAYER_ID, - DISCORD_SERVER_INVITE_URL, - DONATE_URL, - GITHUB_REPO_URL, -} from '../../config' -import type { Action } from '../actions/action' -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 type { GameScene } from '../scenes' -import { ChopTreeScript } from '../scripts/chopTreeScript' -import { MineStoneScript } from '../scripts/mineStoneScript' -import { PlantNewTreeScript } from '../scripts/plantNewTreeScript' - -interface ICommandWithAction { - id: string - action: IGameSceneAction - command: string -} - -interface IActionServiceOptions { - scene: GameScene -} - -export class ActionService { - public possibleCommands!: ICommandWithAction[] - public possibleActions!: IGameSceneAction[] - public activeActions!: IGameSceneAction[] - public scene: GameScene - - constructor({ scene }: IActionServiceOptions) { - this.scene = scene - - void this.initActions() - void this.initCommands() - } - - async initCommands() { - this.possibleCommands - = (await this.scene.game.repository.findAllChatCommands()) as ICommandWithAction[] - } - - async initActions() { - this.possibleActions = [ - 'HELP', - 'GIFT', - 'TRADE', - 'DONATE', - 'REFUEL', - 'STEAL_FUEL', - 'CHOP', - 'MINE', - 'PLANT', - 'START_GROUP_BUILD', - 'DISBAND_GROUP', - 'JOIN_GROUP', - 'START_POLL', - 'VOTE', - 'START_CHANGING_SCENE', - 'START_RAID', - 'CREATE_NEW_PLAYER', - 'START_CREATING_NEW_ADVENTURE', - 'SHOW_MESSAGE', - 'GITHUB', - ] - this.activeActions = this.possibleActions - } - - public findActionByCommand(command: string) { - return this.possibleCommands.find((a) => a.command === command) - } - - public findDynamicActionByCommand(command: string) { - const quest = this.scene.eventService.findActionByCommandInQuest(command) - if (quest) { - return quest - } - - const poll = this.scene.eventService.findActionByCommandInPoll(command) - if (poll) { - return poll - } - } - - public async handleAction( - action: IGameSceneAction, - playerId: string, - params?: string[], - ) { - const player = await this.scene.findOrCreatePlayer(playerId) - if (!player) { - return ANSWER.NO_PLAYER_ERROR - } - - this.scene.group.join(player) - player.updateLastActionAt() - - if (action === 'SHOW_MESSAGE') { - return this.showMessageAction(player, params) - } - if (action === 'REFUEL') { - return this.refuelAction(player, params) - } - if (action === 'CHOP') { - return this.chopAction(player) - } - if (action === 'MINE') { - return this.mineAction(player) - } - if (action === 'PLANT') { - return this.plantAction(player) - } - if (action === 'START_RAID') { - return this.startRaidAction(player, params) - } - if (action === 'START_CHANGING_SCENE') { - // Admin only - if (player.id !== ADMIN_PLAYER_ID) { - return ANSWER.ERROR - } - return this.startChangingSceneAction(player, params) - } - if (action === 'START_GROUP_BUILD') { - // Admin only - if (player.id !== ADMIN_PLAYER_ID) { - return ANSWER.ERROR - } - return this.startGroupBuildAction(player, params) - } - if (action === 'DISBAND_GROUP') { - // Admin only - if (player.id !== ADMIN_PLAYER_ID) { - return ANSWER.ERROR - } - return this.disbandGroupAction() - } - if (action === 'STEAL_FUEL') { - return this.stealFuelAction(player) - } - if (action === 'HELP') { - return this.helpAction(player) - } - if (action === 'GITHUB') { - return this.githubAction(player) - } - if (action === 'DONATE') { - return this.donateAction(player) - } - if (action === 'GIFT') { - return this.giftAction(player, params) - } - if (action === 'TRADE') { - return this.tradeAction(player, params) - } - if (action === 'CREATE_IDEA') { - return this.createIdeaAction(player, params) - } - - return ANSWER.ERROR - } - - public async handleDynamicAction( - action: Action, - playerId: string, - params: string[], - ): Promise { - const player = await this.scene.findOrCreatePlayer(playerId) - if (!player) { - return ANSWER.NO_PLAYER_ERROR - } - - this.scene.group.join(player) - player.updateLastActionAt() - - const answer = await action.live(player, params) - if (answer) { - return answer - } - - return ANSWER.ERROR - } - - public getAvailableCommands() { - const commands: string[] = [] - for (const action of this.activeActions) { - if (action === 'HELP') { - commands.push('!помощь') - } - if (action === 'REFUEL') { - commands.push('!заправить [кол-во]') - } - if (action === 'CHOP') { - commands.push('!рубить') - } - if (action === 'MINE') { - commands.push('!добыть') - } - if (action === 'GIFT') { - commands.push('!подарить [название] [кол-во]') - } - if (action === 'DONATE') { - commands.push('!донат') - } - } - - return commands - } - - public isActionPossible(action: IGameSceneAction): boolean { - return !!this.activeActions.find((a) => a === action) - } - - private startRaidAction(player: Player, params?: string[]) { - // First param is raidersCount - const raidersCount = params ? Number(params[0]) : 0 - - this.scene.eventService.init({ - title: 'The raid has started!', - description: '', - type: 'RAID_STARTED', - secondsToEnd: 60 * 5, - }) - this.scene.initRaiders(raidersCount) - - // Raider points - void player.addRaiderPoints(raidersCount) - - return ANSWER.OK - } - - private async showMessageAction(player: Player, params?: string[]) { - if (!this.isActionPossible('SHOW_MESSAGE')) { - return ANSWER.ERROR - } - - if (!params || !params[0]) { - return ANSWER.ERROR - } - - const message = params[0] - player.addMessage(message) - - return ANSWER.OK - } - - private async stealFuelAction(player: Player) { - if (!this.isActionPossible('STEAL_FUEL')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - this.scene.wagonService.wagon.emptyFuel() - - await player.addVillainPoints(1) - - return { - ok: true, - message: `${player.userName}, and you're a villain!`, - } - } - - private async refuelAction(player: Player, params?: string[]) { - if (!this.isActionPossible('REFUEL')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - if (!params) { - return ANSWER.NO_TARGET_ERROR - } - - const count = this.getAmountFromChatCommand(params[0]) - if (!count) { - return ANSWER.WRONG_AMOUNT_ERROR - } - - const items = player.inventory?.items ?? [] - const itemExist = items.find((item) => item.type === 'WOOD') - if (!itemExist) { - return { - ok: false, - message: `${player.userName}, you don't have wood.`, - } - } - - const isSuccess = await player.inventory?.reduceOrDestroyItem( - itemExist.type, - count, - ) - if (!isSuccess) { - return { - ok: false, - message: `${player.userName}, not enough wood.`, - } - } - - await player.addRefuellerPoints(count) - - this.scene.wagonService.wagon.refuel(count) - - return { - ok: true, - message: `${player.userName}, you helped refuel the Wagon.`, - } - } - - getAmountFromChatCommand(text: string): number | null { - if (typeof Number(text) === 'number' && Number(text) > 0) { - return Math.round(Number(text)) - } - - return null - } - - private async chopAction(player: Player) { - if (!this.isActionPossible('CHOP')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - if (player.script && !player.script.isInterruptible) { - return ANSWER.BUSY_ERROR - } - - const target = this.scene.getTreeToChop() - if (!target) { - return ANSWER.NO_AVAILABLE_TREE_ERROR - } - - const chopTreeFunc = (): boolean => { - void player.chopTree() - if (!player.target || player.target.state === 'DESTROYED') { - player.state = 'IDLE' - if (player.target instanceof Tree) { - void player.inventory.addOrCreateItem('WOOD', player.target?.resource) - } - return true - } - return false - } - - player.script = new ChopTreeScript({ - object: player, - target, - chopTreeFunc, - }) - - return ANSWER.OK - } - - private async mineAction(player: Player) { - if (!this.isActionPossible('MINE')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - if (player.script && !player.script.isInterruptible) { - return { - ok: false, - message: `${player.userName}, you're busy right now.`, - } - } - - const target = this.scene.getStoneToMine() - if (!target) { - return { - ok: false, - message: `${player.userName}, there is no available stone.`, - } - } - - const mineStoneFunc = (): boolean => { - void player.mineStone() - if (!player.target || player.target.state === 'DESTROYED') { - player.state = 'IDLE' - if (player.target instanceof Stone) { - void player.inventory.addOrCreateItem( - 'STONE', - player.target?.resource, - ) - } - return true - } - return false - } - - player.script = new MineStoneScript({ - object: player, - target, - mineStoneFunc, - }) - - return ANSWER.OK - } - - private plantAction(player: Player) { - if (!this.isActionPossible('PLANT')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - if (player.script && !player.script.isInterruptible) { - return { - ok: false, - message: `${player.userName}, you're busy right now.`, - } - } - - if (this.scene.chunkNow instanceof Village) { - const target = this.scene.chunkNow.checkIfNeedToPlantTree() - if (!target) { - return { - ok: false, - message: `${player.userName}, no space available.`, - } - } - - const plantNewTreeFunc = () => { - if (this.scene.chunkNow instanceof Village) { - this.scene.chunkNow.plantNewTree(target) - } - } - - player.script = new PlantNewTreeScript({ - object: player, - target, - plantNewTreeFunc, - }) - - return ANSWER.OK - } - - return ANSWER.ERROR - } - - private startChangingSceneAction(_: Player, params?: string[]) { - if (!this.isActionPossible('START_CHANGING_SCENE')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - if (!params) { - return ANSWER.NO_TARGET_ERROR - } - - const scene = this.getSceneTypeFromChatCommand(params[1]) - if (!scene) { - return ANSWER.NO_TARGET_ERROR - } - - this.scene.eventService.init({ - type: 'SCENE_CHANGING_STARTED', - title: 'Changing location', - description: '', - scene, - secondsToEnd: 10, - }) - - return ANSWER.OK - } - - getSceneTypeFromChatCommand(text: string): GameSceneType | null { - if (text === 'деревня' || text === 'деревню') { - return 'VILLAGE' - } - if (text === 'защиту' || text === 'защита') { - return 'DEFENCE' - } - - return null - } - - private startGroupBuildAction(_: Player, params?: string[]) { - if (!this.isActionPossible('START_GROUP_BUILD')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - if (!params) { - return ANSWER.NO_TARGET_ERROR - } - - const scene = this.getSceneTypeFromChatCommand(params[1]) - if (!scene) { - return ANSWER.NO_TARGET_ERROR - } - - this.scene.group = new Group() - - this.scene.eventService.init({ - type: 'GROUP_FORM_STARTED', - title: 'The group is recruiting!', - description: '', - scene, - secondsToEnd: 120, - }) - - return ANSWER.OK - } - - private disbandGroupAction() { - if (!this.isActionPossible('DISBAND_GROUP')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - this.scene.group?.disband() - - return { - ok: true, - message: 'The group has been disbanded!', - } - } - - private helpAction(player: Player) { - if (!this.isActionPossible('HELP')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - return { - ok: true, - message: `${player.userName}, this is an interactive chat game that any viewer can participate in! Basic commands: !chop, !mine. The remaining commands appear in events (on the right of the screen). Join our community: ${DISCORD_SERVER_INVITE_URL}`, - } - } - - private githubAction(player: Player) { - if (!this.isActionPossible('GITHUB')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - return { - ok: true, - message: `${player.userName}, the game code is in the repository: ${GITHUB_REPO_URL}`, - } - } - - private donateAction(player: Player) { - if (!this.isActionPossible('DONATE')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - return { - ok: true, - message: `${player.userName}, support the game: ${DONATE_URL}`, - } - } - - private async giftAction(player: Player, params: string[] | undefined) { - if (!this.isActionPossible('GIFT')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - if (!params) { - return { - ok: false, - message: `${player.userName}, be more specific.`, - } - } - - const item = this.getItemTypeFromChatCommand(params[0]) - if (!item) { - return { - ok: false, - message: `${player.userName}, be more specific.`, - } - } - - const amount = this.getAmountFromChatCommand(params[1]) - if (!amount) { - return { - ok: false, - message: `${player.userName}, be more specific.`, - } - } - - let warehouse: Warehouse | undefined - if (this.scene.chunkNow instanceof Village) { - warehouse = this.scene.chunkNow.getWarehouse() - } - - if (item === 'WOOD') { - const isSuccess = await player.inventory.reduceOrDestroyItem(item, amount) - if (!isSuccess) { - return { - ok: false, - message: `${player.userName}, you don't have enough wood.`, - } - } - - await warehouse?.inventory.addOrCreateItem(item, amount) - await player.addReputation(amount) - - return { - ok: true, - message: `${player.userName}, you gave wood to the village! Your reputation has increased.`, - } - } - if (item === 'STONE') { - const isSuccess = await player.inventory.reduceOrDestroyItem(item, amount) - if (!isSuccess) { - return { - ok: false, - message: `${player.userName}, you don't have enough stone.`, - } - } - - await warehouse?.inventory.addOrCreateItem(item, amount) - await player.addReputation(amount) - - return { - ok: true, - message: `${player.userName}, you gave stones to the village! Your reputation has increased.`, - } - } - - return { - ok: false, - message: `${player.userName}, be more specific.`, - } - } - - getItemTypeFromChatCommand(text: string): ItemType | null { - if (text === 'wood') { - return 'WOOD' - } - if (text === 'stone') { - return 'STONE' - } - if (text === 'axe') { - return 'AXE' - } - if (text === 'pickaxe') { - return 'PICKAXE' - } - - return null - } - - private async tradeAction(player: Player, params: string[] | undefined) { - if (!this.isActionPossible('TRADE')) { - return ANSWER.CANT_DO_THIS_NOW_ERROR - } - - if (!params) { - return ANSWER.NO_TARGET_ERROR - } - - const amount = this.getAmountFromChatCommand(params[1]) - if (!amount) { - return ANSWER.WRONG_AMOUNT_ERROR - } - - const status = await this.scene.tradeService.findActiveOfferAndTrade( - params[0], - amount, - player, - ) - if (status === 'OFFER_ERROR') { - return { - ok: false, - message: 'Something is wrong. The deal fell through.', - } - } - if (status === 'OFFER_NOT_FOUND') { - return ANSWER.NO_TARGET_ERROR - } - - return { - ok: true, - message: `${player.userName}, successful trade deal!`, - } - } - - private createIdeaAction(player: Player, params: string[] | undefined) { - const text = params ? params[0] : '' - - this.scene.eventService.init({ - title: 'New idea from Twitch Viewer!', - description: `${player.userName}: ${text}`, - type: 'IDEA_CREATED', - secondsToEnd: 60 * 3, - }) - - return ANSWER.OK - } -} diff --git a/apps/api/src/game/services/eventService.ts b/apps/api/src/game/services/eventService.ts deleted file mode 100644 index 447123df..00000000 --- a/apps/api/src/game/services/eventService.ts +++ /dev/null @@ -1,260 +0,0 @@ -import type { - GameSceneType, - IGameEvent, - IGamePoll, - IGameQuest, - IGameQuestTask, -} from '../../../../../packages/api-sdk/src' -import type { Action } from '../actions/action' -import { Village } from '../chunks' -import { Event } from '../common' -import type { GameScene } from '../scenes' -import { PollService } from './pollService' -import { QuestService } from './questService' - -interface IEventServiceOptions { - scene: GameScene -} - -export class EventService { - public events: Event[] = [] - public questService: QuestService - public pollService: PollService - public scene: GameScene - - constructor({ scene }: IEventServiceOptions) { - this.scene = scene - this.questService = new QuestService({ scene }) - this.pollService = new PollService({ scene }) - } - - public update() { - for (const event of this.events) { - const status = event.checkStatus() - - if (status === 'STOPPED') { - this.handleEnding(event) - this.destroy(event) - } - - this.updateSuccessPollsWithQuest(event) - this.updateClosedQuests(event) - } - - this.pollService.update() - this.questService.update() - } - - public init({ - title, - description, - type, - secondsToEnd, - scene, - poll, - quest, - offers, - }: { - title: IGameEvent['title'] - description: IGameEvent['description'] - type: IGameEvent['type'] - secondsToEnd: number - scene?: GameSceneType - poll?: IGameEvent['poll'] - quest?: IGameEvent['quest'] - offers?: IGameEvent['offers'] - }) { - const event = new Event({ - title, - description, - type, - secondsToEnd, - scene, - poll, - 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, - })) - } - - prepareQuestData(quest: IGameQuest | undefined) { - if (!quest) { - return - } - - const tasks = quest?.tasks.map((t) => { - const action = t.action - ? { - ...t.action, - live: undefined, - scene: undefined, - } - : undefined - return { ...t, action } - }) - - return { - ...quest, - tasks, - } - } - - preparePollData(poll: IGamePoll | undefined) { - if (!poll) { - return - } - - return { - ...poll, - action: { - ...poll.action, - poll: undefined, - live: undefined, - scene: undefined, - }, - scene: undefined, - } - } - - public destroy(event: Event) { - const index = this.events.indexOf(event) - this.events.splice(index, 1) - } - - public findActionByCommandInQuest(command: string) { - for (const event of this.events) { - if (event.quest?.tasks) { - const task = event.quest.tasks.find( - (q) => q.action?.command === command, - ) - if (task?.action) { - return task.action as Action - } - } - } - } - - public findActionByCommandInPoll(command: string) { - for (const event of this.events) { - if (event.poll?.action && event.poll.action.command === command) { - return event.poll?.action as Action - } - } - } - - private handleEnding(event: Event) { - if (event.type === 'SCENE_CHANGING_STARTED' && event.scene) { - this.scene.game.initScene(event.scene) - } - if (event.type === 'GROUP_FORM_STARTED' && event.scene) { - this.scene.game.initScene(event.scene) - } - if (event.type === 'RAID_STARTED') { - this.scene.stopRaid() - } - if (event.type === 'TRADE_STARTED') { - this.scene.tradeService.handleTradeIsOver() - } - if (event.type === 'VOTING_FOR_NEW_MAIN_QUEST_STARTED') { - this.scene.tradeService.handleTradeIsOver() - } - } - - private destroyAllEventsWithPoll() { - for (const event of this.events) { - if (event.poll) { - this.destroy(event) - } - } - } - - private updateSuccessPollsWithQuest(event: Event) { - if (event.poll?.status !== 'SUCCESS' || !event.quest) { - return - } - - const updateProgress1: IGameQuestTask['updateProgress'] = () => { - if ( - !this.scene.wagonService.routeService.route?.flags - && this.events.find((e) => e.type === 'MAIN_QUEST_STARTED') - ) { - return { - status: 'SUCCESS', - } - } - - const items - = this.scene.wagonService.wagon.cargo?.checkIfAlreadyHaveItem('WOOD') - if (!items) { - return { - status: 'FAILED', - progressNow: 0, - } - } - - return { - status: 'ACTIVE', - progressNow: items.amount, - } - } - - const tasks = [ - this.questService.createTask({ - updateProgress: updateProgress1, - description: 'Transport cargo safely', - progressNow: 100, - progressToSuccess: 60, - }), - ] - - this.init({ - title: 'Journey', - description: '', - type: 'MAIN_QUEST_STARTED', - secondsToEnd: event.quest.conditions.limitSeconds ?? 9999999, - quest: { - ...event.quest, - status: 'ACTIVE', - tasks, - }, - }) - - // Cargo - this.scene.wagonService.wagon.setCargo() - - if (this.scene.chunkNow instanceof Village) { - this.scene.wagonService.routeService.generateAdventure( - this.scene.chunkNow, - event.quest.conditions.chunks ?? 3, - ) - } - - this.scene.tradeService.traderIsMovingWithWagon = true - - this.destroyAllEventsWithPoll() - } - - private updateClosedQuests(event: Event) { - if (event.status === 'STARTED' && event.quest) { - if (event.quest.status === 'FAILED' || event.quest.status === 'SUCCESS') { - // - event.status = 'STOPPED' - } - } - } -} diff --git a/apps/api/src/game/services/pollService.ts b/apps/api/src/game/services/pollService.ts deleted file mode 100644 index 1e7f99dc..00000000 --- a/apps/api/src/game/services/pollService.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { - IGameObjectPlayer, - IGamePoll, -} from '../../../../../packages/api-sdk/src' -import type { Player } from '../objects/units' -import type { GameScene } from '../scenes' - -interface IPollServiceOptions { - scene: GameScene -} - -export class PollService { - public scene: GameScene - - constructor({ scene }: IPollServiceOptions) { - this.scene = scene - } - - public update() { - for (const event of this.scene.eventService.events) { - if (!event.poll || event.poll.status !== 'ACTIVE') { - continue - } - - if (event.poll.votes.length >= event.poll.votesToSuccess) { - event.poll.status = 'SUCCESS' - } - } - } - - public findActivePollAndVote(pollId: string, player: Player) { - for (const event of this.scene.eventService.events) { - if (event.poll && event.poll?.id === pollId) { - const voted = this.vote(event.poll, player) - if (!voted) { - return 'VOTED_ALREADY' - } - return 'VOTE_SUCCESS' - } - } - - return 'POLL_NOT_FOUND' - } - - private vote(poll: IGamePoll, player: IGameObjectPlayer): boolean { - if (poll.votes.find((v) => v.id === player.id)) { - return false - } - - poll.votes.push({ id: player.id, userName: player.userName }) - return true - } -} diff --git a/apps/api/src/game/services/questService.ts b/apps/api/src/game/services/questService.ts deleted file mode 100644 index 1bc0bcb3..00000000 --- a/apps/api/src/game/services/questService.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { - IGameQuest, - IGameQuestTask, - IGameQuestTaskFunc, -} from '../../../../../packages/api-sdk/src' -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 { GameScene } from '../scenes' - -interface IQuestServiceOptions { - scene: GameScene -} - -export class QuestService { - public scene: GameScene - - constructor({ scene }: IQuestServiceOptions) { - this.scene = scene - } - - public update() { - this.updateAndFinishActiveQuests() - - if (this.scene.chunkNow instanceof Village) { - this.generateNewSideQuest() - } - - for (const event of this.scene.eventService.events) { - if (!event.quest) { - continue - } - - for (const task of event.quest.tasks) { - if (task.status === 'ACTIVE') { - this.updateQuestActiveTask(task) - } - } - } - } - - public create({ - status, - type, - tasks, - conditions, - creatorId, - title, - description, - }: Omit): IGameQuest { - return { - id: createId(), - status, - title, - description, - type, - tasks, - conditions, - creatorId, - } - } - - public createTask({ - updateProgress, - progressToSuccess, - progressNow, - description, - }: Pick< - IGameQuestTask, - 'description' | 'progressToSuccess' | 'progressNow' | 'updateProgress' - >): IGameQuestTask { - return { - id: createId(), - status: 'ACTIVE', - description, - progressNow, - progressToSuccess, - updateProgress, - } - } - - private updateQuestActiveTask(task: IGameQuestTask) { - const progress = task.updateProgress(task.progressToSuccess) - - if (typeof progress.status !== 'undefined') { - task.status = progress.status - } - if (typeof progress.progressNow !== 'undefined') { - task.progressNow = progress.progressNow - } - if (typeof progress.progressToSuccess !== 'undefined') { - task.progressToSuccess = progress.progressToSuccess - } - } - - private updateAndFinishActiveQuests() { - for (const event of this.scene.eventService.events) { - if (!event.quest || event.quest.status !== 'ACTIVE') { - continue - } - - // Tasks done? - if (!event.quest.tasks.find((t) => t.status === 'ACTIVE')) { - // - this.scene.wagonService.wagon.emptyCargo() - this.scene.tradeService.traderIsMovingWithWagon = false - - if (!event.quest.tasks.find((t) => t.status === 'FAILED')) { - // Reward - event.quest.status = 'SUCCESS' - continue - } - - event.quest.status = 'FAILED' - } - } - } - - private generateSecondSideQuest() { - const sideQuests = this.scene.eventService.events.filter( - (e) => e.type === 'SIDE_QUEST_STARTED', - ) - if (sideQuests.length >= 1) { - return - } - - const taskUpdateFunc1: IGameQuestTask['updateProgress'] = () => { - if (this.scene.chunkNow instanceof Village) { - const treesAmount = this.scene.chunkNow.getTreesAmount() - if (treesAmount >= 30) { - return { - status: 'SUCCESS', - progressNow: treesAmount, - } - } - - return { - status: 'ACTIVE', - progressNow: treesAmount, - } - } - - return { - status: 'ACTIVE', - } - } - - const taskAction1 = new PlantTreeAction({ scene: this.scene }) - - const quest = new TreesAreRunningOutQuest({ - creatorId: '1', - taskUpdateFunc1, - taskAction1, - }) - - this.scene.eventService.init({ - type: 'SIDE_QUEST_STARTED', - title: quest.title, - description: quest.description, - secondsToEnd: 9999999, - quest, - }) - } - - private generateNewSideQuest() { - if (!this.scene.chunkNow) { - return - } - - if (this.scene.chunkNow instanceof Village) { - const store = this.scene.chunkNow.getStore() - if (store) { - const notEnough = this.scene.chunkNow.checkIfThereAreNotEnoughTrees() - if (notEnough) { - return this.generateSecondSideQuest() - } - - return - } - } - - const sideQuests = this.scene.eventService.events.filter( - (e) => e.type === 'SIDE_QUEST_STARTED', - ) - if (sideQuests.length >= 1) { - return - } - - const taskUpdateFunc1: IGameQuestTaskFunc = () => { - if (this.scene.chunkNow instanceof Village) { - const warehouse = this.scene.chunkNow.getWarehouse() - if (warehouse) { - const wood = warehouse.getItemByType('WOOD') - if (wood?.amount) { - if (wood.amount >= 25) { - return { - status: 'SUCCESS', - progressNow: wood.amount, - } - } - - return { - status: 'ACTIVE', - progressNow: wood.amount, - } - } - } - } - - return { - status: 'ACTIVE', - } - } - - const taskAction1 = new DonateWoodToVillageAction({ scene: this.scene }) - - const taskUpdateFunc2: IGameQuestTaskFunc = () => { - if (this.scene.chunkNow instanceof Village) { - const store = this.scene.chunkNow.getStore() - if (store) { - return { - status: 'SUCCESS', - progressNow: true, - } - } - } - - return { - status: 'ACTIVE', - progressNow: false, - } - } - - const quest = new NoTradingPostQuest({ - creatorId: '1', - taskUpdateFunc1, - taskUpdateFunc2, - taskAction1, - }) - - this.scene.eventService.init({ - type: 'SIDE_QUEST_STARTED', - title: quest.title, - description: quest.description, - secondsToEnd: 9999999, - quest, - }) - } -} diff --git a/apps/api/src/game/services/routeService.ts b/apps/api/src/game/services/routeService.ts deleted file mode 100644 index 841963e5..00000000 --- a/apps/api/src/game/services/routeService.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { - type IGameChunkTheme, - type IGameRoute, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Forest, LakeChunk, Village } from '../chunks' -import { Route } from '../common' -import { Stone, Tree } from '../objects' -import type { GameScene } from '../scenes' - -interface IRouteServiceOptions { - scene: GameScene -} - -export class RouteService { - public route: Route | undefined - public scene: GameScene - - constructor({ scene }: IRouteServiceOptions) { - this.scene = scene - } - - public 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() - 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(), - }) - 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, - }: { - center: { x: number, y: number } - width: number - height: number - theme: IGameChunkTheme - }) { - return new Village({ width, height, center, theme }) - } - - generateRandomForest({ - center, - width, - height, - theme, - }: { - center: { x: number, y: number } - width: number - height: number - theme: IGameChunkTheme - }) { - const forest = new Forest({ 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({ 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/apps/api/src/game/services/tradeService.ts b/apps/api/src/game/services/tradeService.ts deleted file mode 100644 index e50c8d5d..00000000 --- a/apps/api/src/game/services/tradeService.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { - type ITradeOffer, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Village } from '../chunks' -import { Poll } from '../common' -import { Flag } from '../objects' -import type { Player } from '../objects/units' -import { Trader } from '../objects/units/trader' -import type { GameScene } from '../scenes' -import { MoveOffScreenAndSelfDestroyScript } from '../scripts/moveOffScreenAndSelfDestroyScript' -import { MoveToTargetScript } from '../scripts/moveToTargetScript' -import { MoveToTradePostAndTradeScript } from '../scripts/moveToTradePostAndTradeScript' - -interface ITradeServiceOptions { - scene: GameScene -} - -export class TradeService { - public offers: ITradeOffer[] = [] - public tradeWasSuccessful: boolean - public traderIsMovingWithWagon: boolean - public scene: GameScene - - constructor({ scene }: ITradeServiceOptions) { - this.scene = scene - this.traderIsMovingWithWagon = false - this.tradeWasSuccessful = false - } - - public update() { - this.checkAndGenerateTrader() - this.checkClosedOffers() - } - - 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() - } - } - - public async findActiveOfferAndTrade( - offerId: string, - amount: number, - player: Player, - ) { - for (const offer of this.offers) { - if (offer.id === offerId) { - const status = await this.trade(offer, amount, player) - if (!status) { - return 'OFFER_ERROR' - } - return 'OFFER_SUCCESS' - } - } - - return 'OFFER_NOT_FOUND' - } - - public async trade( - offer: ITradeOffer, - amount: number, - player: Player, - ): Promise { - if (offer.amount < amount) { - return false - } - - if (offer.type === 'BUY') { - const item = player.inventory.checkIfAlreadyHaveItem(offer.item) - if (!item || item.amount < amount) { - return false - } - - await player.updateCoins(offer.unitPrice * amount) - await player.inventory.reduceOrDestroyItem(item.type, amount) - - offer.amount -= amount - return true - } - - return false - } - - public updateTrader(object: Trader) { - object.live() - - if (!object.script) { - if (this.traderIsMovingWithWagon) { - // Moving near Wagon - const random = getRandomInRange(1, 150) - if (random <= 1) { - const target = this.scene.wagonService.findRandomNearFlag() - object.script = new MoveToTargetScript({ - object, - target, - }) - return - } - } - - // Moving to Trade - if (this.checkIfNeedToStartTrade()) { - if (this.scene.chunkNow instanceof Village) { - const target = this.getTradePointFlag() - if (target) { - const startTradeFunc = () => { - this.scene.eventService.init({ - title: 'Trade in progress', - description: '', - type: 'TRADE_STARTED', - secondsToEnd: 60 * 6, - offers: this.offers, - }) - } - - object.script = new MoveToTradePostAndTradeScript({ - object, - target, - startTradeFunc, - }) - } - } - } - } - } - - public handleTradeIsOver() { - const trader = this.getTrader() - if (!trader) { - return - } - - const target = this.scene.wagonService.findRandomOutFlag() - const selfDestroyFunc = () => { - this.scene.removeObject(trader) - } - - trader.script = new MoveOffScreenAndSelfDestroyScript({ - object: trader, - target, - selfDestroyFunc, - }) - - this.removeTrade() - } - - public removeTrade() { - this.offers = [] - for (const event of this.scene.eventService.events) { - if (event.type === 'TRADE_STARTED') { - this.scene.eventService.destroy(event) - } - } - } - - private checkIfNeedToStartTrade(): boolean { - if (this.tradeWasSuccessful) { - return false - } - - const activeTrade = this.scene.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 - } - - private createTradeTargetFlagNearStore() { - if (this.getTradePointFlag()) { - return - } - - const store = this.getStore() - if (!store) { - return - } - - const flag = new Flag({ - x: store.x + 105, - y: store.y + 10, - type: 'TRADE_POINT', - }) - this.scene.chunkNow?.objects.push(flag) - } - - private checkAndGenerateTrader() { - if (this.scene.chunkNow instanceof Village) { - const store = this.scene.chunkNow.getStore() - const trader = this.getTrader() - if (store?.id && !trader?.id) { - this.createTradeTargetFlagNearStore() - this.generateNewTrader() - this.generateTradeOffers() - } - } - } - - private checkClosedOffers() { - for (const offer of this.offers) { - if (offer.amount === 0) { - this.tradeWasSuccessful = true - this.removeTrade() - this.generateNewMainQuest() - } - } - } - - private generateNewTrader() { - const { x, y } = this.scene.wagonService.findRandomOutFlag() - const trader = new Trader({ x, y }) - - this.scene.objects.push(trader) - } - - private generateTradeOffers() { - const offersAmount = 1 - const offers = [] - - for (let i = 1; i <= offersAmount; i++) { - const id = this.generateId() - const commandToTrade = `!trade ${id}` - - const offer: ITradeOffer = { - id, - type: 'BUY', - amount: 25, - unitPrice: 1, - item: 'WOOD', - commandToTrade, - } - - offers.push(offer) - } - - this.offers.push(...offers) - this.tradeWasSuccessful = false - } - - private generateId(startId = 1): string { - const id = startId - for (const offer of this.offers) { - if (offer.id === id.toString()) { - const nextTry = id + 1 - return this.generateId(nextTry) - } - } - return id.toString() - } - - generateNewMainQuest() { - const trader = this.getTrader() - if (!trader) { - return - } - - const store = this.getStore() - if (!store) { - return - } - - const votingEvents = this.scene.eventService.events.filter( - (e) => e.type === 'VOTING_FOR_NEW_MAIN_QUEST_STARTED', - ) - if (votingEvents.length >= 1) { - return - } - - const adventureEvents = this.scene.eventService.events.filter( - (e) => e.type === 'MAIN_QUEST_STARTED', - ) - if (adventureEvents.length >= 1) { - return - } - - const votesToSuccess - = this.scene.findActivePlayers().length >= 2 - ? this.scene.findActivePlayers().length - : 1 - - const poll = new Poll({ votesToSuccess, scene: this.scene }) - - this.scene.eventService.init({ - 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({ - status: 'INACTIVE', - type: 'MAIN', - title: 'Transport cargo to a neighboring village', - description: - 'The merchant is worried about the safety of the items in the chest.', - creatorId: trader.id, - tasks: [], - conditions: { - chunks: getRandomInRange(3, 5), - limitSeconds: 3000, - }, - }), - poll, - }) - } -} diff --git a/apps/api/src/game/services/wagonService.ts b/apps/api/src/game/services/wagonService.ts deleted file mode 100644 index 1eb2b9aa..00000000 --- a/apps/api/src/game/services/wagonService.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - getMinusOrPlus, - getRandomInRange, -} from '../../../../../packages/api-sdk/src' -import { Flag, Wagon } from '../objects' -import type { GameScene } from '../scenes' -import { RouteService } from './routeService' - -interface IWagonServiceOptions { - scene: GameScene -} - -export class WagonService { - public wagon!: Wagon - public outFlags: Flag[] = [] - public nearFlags: Flag[] = [] - public routeService: RouteService - public scene: GameScene - - constructor({ scene }: IWagonServiceOptions) { - this.scene = scene - this.routeService = new RouteService({ scene }) - } - - public update() { - this.updateWagon() - this.updateFlags() - this.routeService.update() - } - - public initWagon({ x, y }: { x: number, y: number }) { - this.wagon = new Wagon({ x, y }) - - this.initOutFlags(10) - this.initNearFlags() - } - - public findRandomOutFlag() { - return this.outFlags[Math.floor(Math.random() * this.outFlags.length)] - } - - public findRandomNearFlag() { - return this.nearFlags[Math.floor(Math.random() * this.nearFlags.length)] - } - - private updateWagon() { - const collisionObjects - = this.scene.chunkNow?.objects.filter( - (obj) => obj.isOnWagonPath && obj.state !== 'DESTROYED', - ) ?? [] - for (const collisionObject of collisionObjects) { - const isInArea = this.wagon.checkIfPointInCollisionArea({ - x: collisionObject.x, - y: collisionObject.y, - }) - if (isInArea) { - this.wagon.state = 'WAITING' - this.wagon.speed = 0 - this.wagon.handleChange() - return - } - } - - if (this.wagon.fuel <= 1) { - this.wagon.state = 'WAITING' - this.wagon.speed = 0 - this.wagon.handleChange() - return - } - - if (this.wagon.state === 'WAITING') { - this.wagon.state = 'IDLE' - } - if (this.wagon.state === 'IDLE') { - const target = this.routeService.route?.getNextFlag() - if (target) { - this.wagon.target = target - this.wagon.state = 'MOVING' - } - } - if (this.wagon.state === 'MOVING') { - this.wagon.speed = 0.5 - const isMoving = this.wagon.move() - this.wagon.handleChange() - - if (!isMoving) { - if ( - this.wagon.target instanceof Flag - && this.wagon.target.type === 'WAGON_MOVEMENT' - ) { - this.routeService.route?.removeFlag(this.wagon.target) - this.wagon.target = undefined - this.wagon.state = 'IDLE' - this.wagon.speed = 0 - } - } - } - - this.wagon.live() - } - - private 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) { - flag.x = this.wagon.x + flag.offsetX - flag.y = this.wagon.y + flag.offsetY - } - } - - private initOutFlags(count: number) { - for (let i = 0; i < count; i++) { - this.outFlags.push(this.generateRandomOutFlag()) - } - } - - private initNearFlags() { - const flag1 = new Flag({ - type: 'WAGON_NEAR_MOVEMENT', - x: this.wagon.x - 300, - y: this.wagon.y, - offsetX: -300, - offsetY: 0, - }) - const flag2 = new Flag({ - type: 'WAGON_NEAR_MOVEMENT', - x: this.wagon.x - 200, - y: this.wagon.y + 50, - offsetX: -200, - offsetY: 50, - }) - const flag3 = new Flag({ - type: 'WAGON_NEAR_MOVEMENT', - x: this.wagon.x - 100, - y: this.wagon.y + 100, - offsetX: -100, - offsetY: 100, - }) - const flag4 = new Flag({ - type: 'WAGON_NEAR_MOVEMENT', - x: this.wagon.x, - y: this.wagon.y + 200, - offsetX: 0, - offsetY: 200, - }) - this.nearFlags.push(flag1, flag2, flag3, flag4) - } - - private generateRandomOutFlag() { - const minOffsetX = 1800 - const minOffsetY = 1200 - - const offsetX - = getRandomInRange(minOffsetX, minOffsetX * 1.5) * getMinusOrPlus() - const offsetY - = getRandomInRange(minOffsetY, minOffsetY * 1.5) * getMinusOrPlus() - - return new Flag({ - type: 'OUT_OF_SCREEN', - x: this.wagon.x + offsetX, - y: this.wagon.y + offsetY, - offsetX, - offsetY, - }) - } -} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts deleted file mode 100644 index 37c49936..00000000 --- a/apps/api/src/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import { BotController } from './bot/bot.controller' -import { Game } from './game/game' - -// Go-go! -const game = new Game() - -const app = new Hono() - -app.use('/*', cors()) - -app.get('/players/top', async (c) => { - const players = await game.repository.findTopPlayers() - return c.json(players) -}) - -app.get('/village', async (c) => { - const village = await game.repository.findVillage() - return c.json(village) -}) - -app.get('/scene', (c) => { - const scene = game.scene.getInfo() - return c.json(scene) -}) - -const port = 4001 -console.log(`HTTP server: listening on localhost:${port}`) - -// Getting messages from Chat and reacting to them -void new BotController(game).serve() - -export default { - port, - fetch: app.fetch, -} diff --git a/apps/api/src/websocket/websocket.server.ts b/apps/api/src/websocket/websocket.server.ts deleted file mode 100644 index 3d602fe2..00000000 --- a/apps/api/src/websocket/websocket.server.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { WebSocketMessage } from '../../../../packages/api-sdk/src' - -export const server = Bun.serve({ - port: 4002, - fetch(req, server) { - // const cookies = req.headers.get("cookie"); - // const username = getUsernameFromCookies(cookies); - // const success = server.upgrade(req, { data: { username: '123' } }); - if (server.upgrade(req)) { - return undefined - } - - return new Response('Hello world') - }, - websocket: { - open(ws) { - ws.subscribe('game') - console.log('smb connected') - }, - message() { - // const action = MessageController.parseMessage(message.toString()); - // if (!action) { - // return; - // } - // new ActionService(action); - // server.publish("game", `${message}`); - }, - close(ws) { - ws.unsubscribe('game') - }, - }, -}) - -export function sendMessage( - event: WebSocketMessage['event'], - object?: WebSocketMessage['object'], -) { - server.publish('game', JSON.stringify({ id: createId(), event, object })) -} - -console.log(`WebSocket server: listening on ${server.hostname}:${server.port}`) diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json deleted file mode 100644 index 94b096d4..00000000 --- a/apps/api/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "strict": true - }, - "include": ["src"] -} diff --git a/eslint.config.js b/eslint.config.js index 0d588d82..d391b4a3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -17,6 +17,5 @@ export default antfu({ 'node_modules', '.svelte-kit', 'build', - '.dependency-cruiser.cjs', ], }) diff --git a/infra/dev/docker-compose.yaml b/infra/dev/docker-compose.yaml deleted file mode 100644 index 509dff36..00000000 --- a/infra/dev/docker-compose.yaml +++ /dev/null @@ -1,17 +0,0 @@ -services: - postgres: - container_name: postgres - image: postgres:16 - environment: - POSTGRES_DB: db - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - volumes: - - royal-madness-data:/var/lib/postgresql/data - ports: - - '6432:5432' - restart: unless-stopped - -volumes: - royal-madness-data: - name: royal-madness-data diff --git a/infra/docker-prod/docker-compose.yml b/infra/docker-prod/docker-compose.yml deleted file mode 100644 index 2519f859..00000000 --- a/infra/docker-prod/docker-compose.yml +++ /dev/null @@ -1,39 +0,0 @@ -services: - - traefik: - image: traefik:v3.0 - restart: on-failure - container_name: traefik - command: - # - "--log.level=DEBUG" - # - --api.insecure=true - - --providers.docker=true - - --providers.docker.exposedbydefault=false - - '--entryPoints.web.address=:80' - - --entrypoints.web.http.redirections.entryPoint.to=websecure - - --entrypoints.web.http.redirections.entryPoint.scheme=https - - --entrypoints.web.http.redirections.entrypoint.permanent=true - - '--entryPoints.websecure.address=:443' - - --certificatesresolvers.myresolver.acme.httpchallenge=true - - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web - - --certificatesresolvers.myresolver.acme.email=hmbanan666@hotmail.com - - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json - ports: - - '80:80' - - '443:443' - volumes: - - './letsencrypt:/letsencrypt' - - '/var/run/docker.sock:/var/run/docker.sock:ro' - - app: - image: ghcr.io/hmbanan666/chat-game/app:main - restart: on-failure - pull_policy: always - container_name: app-container - env_file: - - .env - labels: - - traefik.enable=true - - traefik.http.routers.app.rule=Host(`chatgame.space`) - - traefik.http.routers.app.entrypoints=websecure - - traefik.http.routers.app.tls.certresolver=myresolver diff --git a/package-lock.json b/package-lock.json index bf071368..75c2a29b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,6 @@ "@radix-ui/colors": "3.0.0", "@twurple/api": "7.1.0", "@twurple/auth": "7.1.0", - "@twurple/easy-bot": "7.1.0", - "@twurple/pubsub": "7.1.0", - "hono": "4.4.7", "howler": "2.2.4", "jsonwebtoken": "9.0.2", "lucide-svelte": "0.395.0", @@ -660,23 +657,6 @@ "tslib": "^2.6.2" } }, - "node_modules/@d-fischer/connection": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@d-fischer/connection/-/connection-9.0.0.tgz", - "integrity": "sha512-Mljp/EbaE+eYWfsFXUOk+RfpbHgrWGL/60JkAvjYixw6KREfi5r17XdUiXe54ByAQox6jwgdN2vebdmW1BT+nQ==", - "dependencies": { - "@d-fischer/isomorphic-ws": "^7.0.0", - "@d-fischer/logger": "^4.2.1", - "@d-fischer/shared-utils": "^3.5.0", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@types/ws": "^8.5.4", - "tslib": "^2.4.1", - "ws": "^8.11.0" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, "node_modules/@d-fischer/cross-fetch": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@d-fischer/cross-fetch/-/cross-fetch-5.0.5.tgz", @@ -685,32 +665,11 @@ "node-fetch": "^2.6.12" } }, - "node_modules/@d-fischer/deprecate": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@d-fischer/deprecate/-/deprecate-2.0.2.tgz", - "integrity": "sha512-wlw3HwEanJFJKctwLzhfOM6LKwR70FPfGZGoKOhWBKyOPXk+3a9Cc6S9zhm6tka7xKtpmfxVIReGUwPnMbIaZg==" - }, "node_modules/@d-fischer/detect-node": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@d-fischer/detect-node/-/detect-node-3.0.1.tgz", "integrity": "sha512-0Rf3XwTzuTh8+oPZW9SfxTIiL+26RRJ0BRPwj5oVjZFyFKmsj9RGfN2zuTRjOuA3FCK/jYm06HOhwNK+8Pfv8w==" }, - "node_modules/@d-fischer/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@d-fischer/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-7eoxnxcto5eVPW5h1T+ePnVFukmI9f/ZR9nlBLh1t3kyzJDUNor2C+YW9H/Terw3YnbZSDgDYrpCJCHtOtAQHw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@d-fischer/isomorphic-ws": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@d-fischer/isomorphic-ws/-/isomorphic-ws-7.0.2.tgz", - "integrity": "sha512-xK+qIJUF0ne3dsjq5Y3BviQ4M+gx9dzkN+dPP7abBMje4YRfow+X9jBgeEoTe5e+Q6+8hI9R0b37Okkk8Vf0hQ==", - "peerDependencies": { - "ws": "^8.2.0" - } - }, "node_modules/@d-fischer/logger": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@d-fischer/logger/-/logger-4.2.3.tgz", @@ -2297,28 +2256,6 @@ "url": "https://github.com/sponsors/d-fischer" } }, - "node_modules/@twurple/chat": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@twurple/chat/-/chat-7.1.0.tgz", - "integrity": "sha512-AzLtq+xqbyYpqPZau5jvX3Dov+C7MW1YTunYZZ5TqyQlEb/leUD6LdwdhXhsVxQUfpVD1FhU1NSlNd7VEhv0Rg==", - "dependencies": { - "@d-fischer/cache-decorators": "^4.0.0", - "@d-fischer/deprecate": "^2.0.2", - "@d-fischer/logger": "^4.2.1", - "@d-fischer/rate-limiter": "^1.0.0", - "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "7.1.0", - "ircv3": "^0.33.0", - "tslib": "^2.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "@twurple/auth": "7.1.0" - } - }, "node_modules/@twurple/common": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@twurple/common/-/common-7.1.0.tgz", @@ -2332,43 +2269,6 @@ "url": "https://github.com/sponsors/d-fischer" } }, - "node_modules/@twurple/easy-bot": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@twurple/easy-bot/-/easy-bot-7.1.0.tgz", - "integrity": "sha512-NBuNIAS3aJYzdDMr48kNbc1RdnmKL/abJf5HJgnlE7PP/ndthQYS3S/RaNkR/DIouvbJAmgSsYRj/7UlyZ3lZQ==", - "dependencies": { - "@d-fischer/logger": "^4.2.1", - "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/api": "7.1.0", - "@twurple/auth": "7.1.0", - "@twurple/chat": "7.1.0", - "@twurple/common": "7.1.0", - "tslib": "^2.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, - "node_modules/@twurple/pubsub": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@twurple/pubsub/-/pubsub-7.1.0.tgz", - "integrity": "sha512-2YMiktQbHPiPqCNzdlQ2OOMLVHNiy5tjRImkVBHNjw0tqCsbrCUhmFwgKjiIiDQUTTzOcNdFhDNhlcgy5Mz65A==", - "dependencies": { - "@d-fischer/connection": "^9.0.0", - "@d-fischer/logger": "^4.2.1", - "@d-fischer/shared-utils": "^3.6.1", - "@d-fischer/typed-event-emitter": "^3.3.0", - "@twurple/common": "7.1.0", - "tslib": "^2.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "@twurple/auth": "7.1.0" - } - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -2434,6 +2334,7 @@ "version": "20.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz", "integrity": "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw==", + "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -2462,14 +2363,6 @@ "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "dev": true }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/scope-manager": { "version": "7.13.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz", @@ -5163,14 +5056,6 @@ "node": ">= 0.4" } }, - "node_modules/hono": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.4.7.tgz", - "integrity": "sha512-WoQWFQyVFEVRtIzP5sHPv7nvIw+RYL/HRnvnOCDxj6A+BtrwuC9S0vryZbV4IyFcNgOJ87r/phDiC1x2eEo4Gg==", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -5278,23 +5163,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "node_modules/ircv3": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/ircv3/-/ircv3-0.33.0.tgz", - "integrity": "sha512-7rK1Aial3LBiFycE8w3MHiBBFb41/2GG2Ll/fR2IJj1vx0pLpn1s+78K+z/I4PZTqCCSp/Sb4QgKMh3NMhx0Kg==", - "dependencies": { - "@d-fischer/connection": "^9.0.0", - "@d-fischer/escape-string-regexp": "^5.0.0", - "@d-fischer/logger": "^4.2.1", - "@d-fischer/shared-utils": "^3.5.0", - "@d-fischer/typed-event-emitter": "^3.3.0", - "klona": "^2.0.5", - "tslib": "^2.4.1" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -8113,7 +7981,8 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true }, "node_modules/unicorn-magic": { "version": "0.1.0", @@ -8499,26 +8368,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/package.json b/package.json index 9e150244..f9129437 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "twitch" ], "scripts": { - "deprecated:api:start": "bun run --hot apps/api/src/index.ts", "dev": "vite dev", "build": "svelte-kit sync && vite build", "preview": "vite preview", @@ -35,9 +34,6 @@ "@radix-ui/colors": "3.0.0", "@twurple/api": "7.1.0", "@twurple/auth": "7.1.0", - "@twurple/easy-bot": "7.1.0", - "@twurple/pubsub": "7.1.0", - "hono": "4.4.7", "howler": "2.2.4", "jsonwebtoken": "9.0.2", "lucide-svelte": "0.395.0", diff --git a/packages/api-sdk/src/index.ts b/packages/api-sdk/src/index.ts deleted file mode 100644 index db25bca0..00000000 --- a/packages/api-sdk/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './lib/types' -export * from './lib/random' -export * from './lib/date' diff --git a/packages/api-sdk/src/lib/actionAnswer.ts b/packages/api-sdk/src/lib/actionAnswer.ts deleted file mode 100644 index 33f5f70f..00000000 --- a/packages/api-sdk/src/lib/actionAnswer.ts +++ /dev/null @@ -1,58 +0,0 @@ -export const ANSWER = { - OK: { - ok: true, - message: null, - }, - DONATE_WOOD_OK: { - ok: true, - message: 'You gave wood to the village! Your reputation has increased.', - }, - VOTED_OK: { - ok: true, - message: 'You voted!', - }, - ERROR: { - ok: false, - message: null, - }, - BUSY_ERROR: { - ok: false, - message: 'You\'re busy right now', - }, - CANT_DO_THIS_NOW_ERROR: { - ok: false, - message: 'This cannot be done now.', - }, - NO_PLAYER_ERROR: { - ok: false, - message: 'You are not in active game :(', - }, - NO_TARGET_ERROR: { - ok: false, - message: 'No target specified.', - }, - NO_SPACE_AVAILABLE_ERROR: { - ok: false, - message: 'No space available.', - }, - NO_AVAILABLE_TREE_ERROR: { - ok: false, - message: 'No available tree', - }, - WRONG_AMOUNT_ERROR: { - ok: false, - message: 'Incorrect quantity specified.', - }, - ALREADY_VOTED_ERROR: { - ok: false, - message: 'You\'ve already voted.', - }, - NOT_ENOUGH_PARAMS_ERROR: { - ok: false, - message: 'Be more specific.', - }, - NOT_ENOUGH_WOOD_ERROR: { - ok: false, - message: 'You don\'t have enough wood.', - }, -} diff --git a/packages/api-sdk/src/lib/date.ts b/packages/api-sdk/src/lib/date.ts deleted file mode 100644 index 708856d6..00000000 --- a/packages/api-sdk/src/lib/date.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function getDateMinusMinutes(minutes: number) { - const milliseconds = minutes * 60 * 1000 - return new Date(new Date().getTime() - milliseconds) -} - -export function getDatePlusSeconds(seconds: number) { - const milliseconds = seconds * 1000 - return new Date(new Date().getTime() + milliseconds) -} diff --git a/packages/api-sdk/src/lib/random.ts b/packages/api-sdk/src/lib/random.ts deleted file mode 100644 index 81a8fdea..00000000 --- a/packages/api-sdk/src/lib/random.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function getRandomInRange(min: number, max: number): number { - const ceilMin = Math.ceil(min) - return Math.floor(Math.random() * (max - ceilMin + 1)) + ceilMin -} - -export function getMinusOrPlus(): number { - // -1 or 1 - return Math.round(Math.random()) * 2 - 1 -} diff --git a/packages/api-sdk/src/lib/types.ts b/packages/api-sdk/src/lib/types.ts deleted file mode 100644 index aee260c5..00000000 --- a/packages/api-sdk/src/lib/types.ts +++ /dev/null @@ -1,399 +0,0 @@ -export interface TwitchAccessTokenResponse { - access_token: string - refresh_token: string - scope: string[] - expires_in: number - token_type: 'bearer' -} - -export interface TwitchAccessToken { - userId: string - accessToken: string - refreshToken: string | null - scope: string[] - expiresIn: number | null - obtainmentTimestamp: number -} - -export interface IGameAction { - command: string - commandDescription: string -} - -export interface IGameActionResponse { - ok: boolean - message: string | null -} - -export type IGameSceneAction = - | 'HELP' - | 'GIFT' - | 'TRADE' - | 'DONATE' - | 'REFUEL' - | 'STEAL_FUEL' - | 'CHOP' - | 'MINE' - | 'PLANT' - | 'START_GROUP_BUILD' - | 'DISBAND_GROUP' - | 'JOIN_GROUP' - | 'START_POLL' - | 'VOTE' - | 'START_CHANGING_SCENE' - | 'START_RAID' - | 'CREATE_NEW_PLAYER' - | 'START_CREATING_NEW_ADVENTURE' - | 'SHOW_MESSAGE' - | 'GITHUB' - | 'CREATE_IDEA' - -export type ItemType = 'WOOD' | 'STONE' | 'AXE' | 'PICKAXE' | 'COIN' - -export interface IGameInventory { - id: string - objectId: string - items: IGameInventoryItem[] -} - -export interface IGameInventoryItem { - id: string - createdAt: Date - updatedAt: Date - inventoryId: string - type: ItemType - amount: number - durability: number -} - -export interface IGameSkill { - id: string - type: 'WOODSMAN' | 'MINER' - objectId: string | null - lvl: number - xp: number - xpNextLvl: number -} - -export interface IGameQuest { - id: string - type: 'MAIN' | 'SIDE' - title: string - description: string - tasks: IGameQuestTask[] - status: 'INACTIVE' | 'ACTIVE' | 'FAILED' | 'SUCCESS' - creatorId: string - conditions: { - chunks?: number - limitSeconds?: number - reward?: string - } -} - -export interface IGameQuestTask { - id: string - description: string - status: 'INACTIVE' | 'ACTIVE' | 'FAILED' | 'SUCCESS' - progressNow: number | boolean - progressToSuccess: number | boolean - updateProgress: IGameQuestTaskFunc - command?: string - action?: IGameAction -} - -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 interface IGameObject { - id: string - x: number - y: number - state: IGameObjectState - direction: IGameObjectDirection - entity: IGameObjectEntity - target: IGameObject | undefined - health: number - speedPerSecond: number - size: number -} - -export type IGameObjectState = - | 'MOVING' - | 'IDLE' - | 'WAITING' - | 'CHOPPING' - | 'MINING' - | 'DESTROYED' -export type IGameObjectEntity = - | 'RABBIT' - | 'WOLF' - | 'PLAYER' - | 'RAIDER' - | 'TREE' - | 'STONE' - | 'WATER' - | 'LAKE' - | 'FLAG' - | 'AREA' - | 'TRADER' - | 'COURIER' - | 'FARMER' - | 'MECHANIC' - | 'WAGON' - | IGameObjectBuildingType -export type IGameObjectDirection = 'LEFT' | 'RIGHT' - -export interface WebSocketMessage { - id: string - event: - | 'OBJECT_UPDATED' - | 'RAID_STARTED' - | 'GROUP_FORM_STARTED' - | 'SCENE_CHANGING_STARTED' - | 'COUNTDOWN_NEXT_WAVE_STARTED' - | 'SCENE_CHANGED' - | 'VOTING_FOR_NEW_MAIN_QUEST_STARTED' - | 'MAIN_QUEST_STARTED' - | 'SIDE_QUEST_STARTED' - | 'TRADE_STARTED' - | 'IDEA_CREATED' - object?: Partial -} - -export interface IGameObjectWagon extends IGameObject { - fuel: number - visibilityArea: { - startX: number - endX: number - startY: number - endY: number - } - cargoType: 'CHEST' | undefined -} - -export type IGameObjectBuildingType = - | 'CAMPFIRE' - | 'WAREHOUSE' - | 'WAGON_STOP' - | 'STORE' - | 'CONSTRUCTION_AREA' - -export interface IGameObjectBuilding extends IGameObject { - inventory: IGameInventory -} - -export interface IGameBuildingCampfire extends IGameObjectBuilding {} - -export interface IGameBuildingWarehouse extends IGameObjectBuilding {} - -export interface IGameBuildingStore extends IGameObjectBuilding {} - -export interface IGameBuildingWagonStop extends IGameObjectBuilding {} - -export interface IGameBuildingConstructionArea extends IGameObjectBuilding {} - -export interface IGameObjectFlag extends IGameObject { - type: - | 'MOVEMENT' - | 'WAGON_MOVEMENT' - | 'WAGON_NEAR_MOVEMENT' - | 'RESOURCE' - | 'SPAWN_LEFT' - | 'SPAWN_RIGHT' - | 'OUT_OF_SCREEN' - | 'TRADE_POINT' -} - -export interface IGameObjectWater extends IGameObject {} - -export interface IGameObjectLake extends IGameObject { - water: IGameObjectWater[] -} - -export interface IGameObjectArea extends IGameObject { - theme: IGameChunkTheme - area: { - startX: number - endX: number - startY: number - endY: number - } -} - -export interface IGameObjectTree extends IGameObject { - type: '1' | '2' | '3' | '4' | '5' - variant: IGameChunkTheme - resource: number - isReadyToChop: boolean -} - -export interface IGameObjectStone extends IGameObject { - type: '1' - resource: number -} - -export interface IGameObjectUnit extends IGameObject { - userName: string - coins: number - inventory: IGameInventory - visual: { - head: '1' - hairstyle: 'BOLD' | 'CLASSIC' | 'COAL_LONG' | 'ORANGE_WITH_BEARD' - top: - | 'VIOLET_SHIRT' - | 'BLACK_SHIRT' - | 'GREEN_SHIRT' - | 'BLUE_SHIRT' - | 'DARK_SILVER_SHIRT' - } - dialogue: { - messages: { id: string, text: string }[] - } -} - -export interface IGameObjectTrader extends IGameObjectUnit {} - -export interface IGameObjectCourier extends IGameObjectUnit {} - -export interface IGameObjectFarmer extends IGameObjectUnit {} - -export interface IGameObjectMechanic extends IGameObjectUnit {} - -export interface IGameObjectPlayer extends IGameObjectUnit { - reputation: number - villainPoints: number - refuellerPoints: number - raiderPoints: number - skills: IGameSkill[] - lastActionAt: Date -} - -export interface IGameObjectRaider extends IGameObjectUnit {} - -export interface IGameObjectRabbit extends IGameObject {} - -export interface IGameObjectWolf extends IGameObject {} - -export interface ITradeOffer { - id: string - type: 'BUY' | 'SELL' - amount: number - unitPrice: number - item: ItemType - commandToTrade: string -} - -export interface IGameScript { - id: string - tasks: IGameTask[] - isInterruptible: boolean - live: () => void -} - -export interface IGameTask { - id: string - status: 'IDLE' | 'ACTIVE' | 'DONE' - target?: IGameObject - 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 - votesToSuccess: number - votes: { id: string, userName: string }[] -} - -export type GameSceneType = 'VILLAGE' | 'DEFENCE' | 'MOVING' - -export interface GetSceneResponse { - id: string - commands: string[] - chunk: IGameChunk | null - events: IGameEvent[] - group: IGameGroup - wagon: IGameObjectWagon - 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[] -} - -export interface PlayerTitle { - title: string - type: - | 'RICH' - | 'FAMOUS' - | 'VIEWER' - | 'RAIDER' - | 'VILLAIN' - | 'REFUELLER' - | 'WOODSMAN' - | 'MINER' -} - -export type GraphicsContainerType = - | 'INTERFACE' - | 'PLAYER_IDLE' - | 'PLAYER_COINS' - | 'PLAYER_WOOD' - | 'PLAYER_STONE' - | 'PLAYER_AXE' - | 'PLAYER_PICKAXE' - | 'UNIT_TOP' - | 'UNIT_HEAD' - | 'UNIT_HAIR' - | 'WAGON_WHEEL' - | 'WAGON_ENGINE' - | 'WAGON_ENGINE_CLOUD' - | 'WAGON_CARGO' - | 'WAGON_FUEL' - | 'FIRE_PARTICLE' diff --git a/packages/api-sdk/src/tsconfig.json b/packages/api-sdk/src/tsconfig.json deleted file mode 100644 index df1c420c..00000000 --- a/packages/api-sdk/src/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "module": "ESNext", - "strict": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": false, - "forceConsistentCasingInFileNames": true - }, - "references": [ - { - "path": "./tsconfig.lib.json" - } - ], - "files": [], - "include": [] -} diff --git a/packages/api-sdk/src/tsconfig.lib.json b/packages/api-sdk/src/tsconfig.lib.json deleted file mode 100644 index b79a8c50..00000000 --- a/packages/api-sdk/src/tsconfig.lib.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "types": ["node"], - "declaration": true, - "outDir": "../../dist/out-tsc" - }, - "include": ["src/**/*.ts"] -} diff --git a/src/lib/game/types.ts b/src/lib/game/types.ts index cebbf8cd..52a79bd1 100644 --- a/src/lib/game/types.ts +++ b/src/lib/game/types.ts @@ -1,6 +1,5 @@ import type { Container, TilingSprite } from 'pixi.js' import type { GameAction } from '$lib/game/actions/interface' - import type { GameChunk, GameChunkService, IGameChunkTheme, @@ -111,23 +110,6 @@ type GameObjectType = | 'WAGON' | GameObjectBuildingType -export interface TwitchAccessTokenResponse { - access_token: string - refresh_token: string - scope: string[] - expires_in: number - token_type: 'bearer' -} - -export interface TwitchAccessToken { - userId: string - accessToken: string - refreshToken: string | null - scope: string[] - expiresIn: number | null - obtainmentTimestamp: number -} - export interface IGameActionResponse { ok: boolean message: string | null diff --git a/src/lib/server/db/db.repository.ts b/src/lib/server/db/db.repository.ts deleted file mode 100644 index 2ceee546..00000000 --- a/src/lib/server/db/db.repository.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import type { TwitchAccessToken } from '../../../../packages/api-sdk/src' -import { db } from './db.client' - -export class DBRepository { - private readonly db - - constructor() { - this.db = db - } - - async getTwitchAccessToken( - userId: string, - ): Promise { - const token = await db.twitchAccessToken.findUnique({ - where: { userId }, - }) - if (!token) { - return null - } - - return { - ...token, - obtainmentTimestamp: Number(token.obtainmentTimestamp), - } - } - - updateTwitchAccessToken(userId: string, token: Partial) { - return db.twitchAccessToken.update({ - where: { userId }, - data: { - ...token, - obtainmentTimestamp: token.obtainmentTimestamp?.toString(), - }, - }) - } - - createTwitchAccessToken(token: TwitchAccessToken) { - return db.twitchAccessToken.create({ - data: { - ...token, - obtainmentTimestamp: token.obtainmentTimestamp.toString(), - }, - }) - } - - findVillage() { - return db.village.findFirst() - } - - async findTopPlayers() { - const famous = await db.player.findFirst({ - orderBy: { reputation: 'desc' }, - }) - const rich = await db.player.findFirst({ - orderBy: { coins: 'desc' }, - }) - const viewer = await db.player.findFirst({ - orderBy: { viewerPoints: 'desc' }, - }) - const raider = await db.player.findFirst({ - orderBy: { raiderPoints: 'desc' }, - }) - const villain = await db.player.findFirst({ - orderBy: { villainPoints: 'desc' }, - }) - const refueller = await db.player.findFirst({ - orderBy: { refuellerPoints: 'desc' }, - }) - const woodsman = await this.findTopWoodsmanPlayer() - const miner = await this.findTopMinerPlayer() - - return { - famous: { - player: famous, - points: famous?.reputation, - }, - rich: { - player: rich, - points: rich?.coins, - }, - viewer: { - player: viewer, - points: viewer?.viewerPoints, - }, - raider: { - player: raider, - points: raider?.raiderPoints, - }, - villain: { - player: villain, - points: villain?.villainPoints, - }, - refueller: { - player: refueller, - points: refueller?.refuellerPoints, - }, - woodsman, - miner, - } - } - - async findTopWoodsmanPlayer() { - const topSkill = await db.skill.findFirst({ - where: { type: 'WOODSMAN' }, - orderBy: { lvl: 'desc' }, - }) - if (!topSkill) { - return null - } - const player = await db.player.findUnique({ - where: { id: topSkill.objectId }, - }) - - return { - player, - points: topSkill.lvl, - } - } - - async findTopMinerPlayer() { - const topSkill = await db.skill.findFirst({ - where: { type: 'MINER' }, - orderBy: { lvl: 'desc' }, - }) - if (!topSkill) { - return null - } - const player = await db.player.findUnique({ - where: { id: topSkill.objectId }, - }) - - return { - player, - points: topSkill.lvl, - } - } - - findPlayerByProfileId(profileId: string) { - return this.db.player.findFirst({ - where: { profileId }, - }) - } - - createPlayer({ - profileId, - name, - inventoryId, - id, - }: { - profileId: string - name: string - inventoryId: string - id: string - }) { - return db.player.create({ - data: { - id, - profileId, - name, - inventoryId, - }, - }) - } - - async findOrCreatePlayer(profileId: string, name: string) { - const player = await this.findPlayerByProfileId(profileId) - if (!player) { - const id = createId() - const inventory = await this.createInventory(id) - return this.createPlayer({ - id, - profileId, - name, - inventoryId: inventory.id, - }) - } - - return player - } - - createInventory(objectId: string) { - return db.inventory.create({ - data: { - id: createId(), - objectId, - }, - }) - } - - addPlayerViewerPoints(id: string, increment: number) { - return db.player.update({ - where: { id }, - data: { - viewerPoints: { increment }, - }, - }) - } - - findAllChatCommands() { - return db.chatCommand.findMany() - } -}