diff --git a/README.md b/README.md index 6c2a4ddc..b2981019 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,45 @@ -# [Twitch Chat Game] Royal Madness 👑 +# 👑 Chat Game for Twitch -đŸ“ē🎮 [Building and playing it live](https://www.twitch.tv/hmbanan666) 👾 [Our community](https://discord.gg/B6etUajrGZ) +- 🏠 [Game Website](https://chatgame.space) +- đŸ“ē [Developing and playing it live](https://www.twitch.tv/hmbanan666) +- 👾 [Our community in Discord](https://discord.gg/B6etUajrGZ) -![Screen](https://github.com/hmbanan666/royal-madness-twitch-game/assets/25910785/a80009a5-ac75-4935-afd2-e1aae16285d6) +![Screen](https://github.com/hmbanan666/chat-game/assets/25910785/a22468a4-0bf1-43e3-91fc-23a1e2a675fc) -🤔 Imagine an open world where your Hero can: +🤔 Imagine procedurally generated world where you and your viewers can: -- đŸ—ēī¸ **Travel**, **defeat** creatures and **find** an infinite number of quests -- 💎 **Gather** loot and a variety of materials -- 🏗ī¸ **Construct** buildings with other Heroes -- đŸ’Ŧ **Chat** with other Heroes in real time -- 🏆 **Earn** more than 1000 achievements! +- đŸ’Ŧ **Use commands** in chat to see actions in real time +- đŸ—ēī¸ **Travel** with the Machine +- đŸĻ„ **Complete** main and side quests from game characters +- 💎 **Gather** a variety of materials +- 🏗ī¸ **Construct** buildings +- 🏆 **Earn** achievements! -Let's build a similar world together! +Let's build a similar world together! ⭐ī¸ Become a Stargazer ⭐ī¸ -## What will be here? +## 🧱 Stack -- [Front] Web client for the game made with **Typescript**, **React** - - simple graphics, top view camera - - interface, menu, inventory slots... -- [Back] Server on **Bun**, where will be a storage for progress of all Heroes +- [PixiJS](https://pixijs.com/): The HTML5 Creation Engine. +- [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. +- [Bun](https://bun.sh/): An all-in-one JavaScript runtime & toolkit designed for speed, complete with a bundler, test runner, and Node.js-compatible package manager. +- [TypeScript](https://www.typescriptlang.org/): A strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. +- [ESLint](https://eslint.org/): Statically analyzes a code to quickly find problems. -## Why? +## 🕹ī¸ How to develop -- I want to take a break from product development and develop some game that myself would play in the evenings. -- It will be made using front and back parts, with good code practices (hope so 😀). -- Want to discover something new in software development. Some new challenges and their overcoming! -- I enjoy the new achievements on GitHub, especially Starstruck x4: "Created a repository that has 4096 stars". Why not - to try? +Clone this repo and use standard commands: -⭐ī¸ Become a Stargazer ⭐ī¸ Star this repo. Make forks, issues, PRs, and have fun! Thanks for your ideas and activity! 😉 +```shell +git clone https://github.com/hmbanan666/chat-game +bun install +bun run dev +``` + +## đŸĒ€ License + +This project is licensed under the MIT License - see the [**MIT License**](https://github.com/hmbanan666/chat-game/blob/main/LICENSE) file for details. \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index bb0dcd74..c6cc083c 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 24482dba..0ee883da 100644 --- a/package.json +++ b/package.json @@ -41,23 +41,23 @@ "howler": "2.2.4", "jsonwebtoken": "9.0.2", "lucide-svelte": "0.395.0", - "pixi.js": "8.1.6" + "pixi.js": "8.1.7" }, "devDependencies": { "@antfu/eslint-config": "2.21.1", - "@sveltejs/kit": "2.5.11", + "@sveltejs/kit": "2.5.16", "@sveltejs/vite-plugin-svelte": "3.1.1", "@types/bun": "1.1.4", "@types/howler": "2.2.11", "@types/jsonwebtoken": "9.0.6", - "eslint": "9.4.0", - "eslint-plugin-svelte": "2.39.3", + "eslint": "9.5.0", + "eslint-plugin-svelte": "2.40.0", "madge": "7.0.0", "prisma": "5.15.0", "svelte": "4.2.18", "svelte-adapter-bun": "0.5.2", - "svelte-check": "3.8.0", - "svelte-eslint-parser": "0.37.0", + "svelte-check": "3.8.1", + "svelte-eslint-parser": "0.39.1", "typescript": "5.4.5", "vite": "5.3.1" } diff --git a/src/lib/game/services/chunk/baseChunk.ts b/src/lib/game/services/chunk/baseChunk.ts index 0cb48fc4..e5aae166 100644 --- a/src/lib/game/services/chunk/baseChunk.ts +++ b/src/lib/game/services/chunk/baseChunk.ts @@ -15,7 +15,7 @@ import type { } from '$lib/game/services/chunk/interface' import { FlagObject } from '$lib/game/objects/flagObject' -interface IGameChunkOptions { +interface BaseChunkOptions { center: GameChunk['center'] title: GameChunk['title'] type: GameChunk['type'] @@ -41,7 +41,7 @@ export class BaseChunk implements GameChunk { height, center, game, - }: IGameChunkOptions) { + }: BaseChunkOptions) { this.id = createId() this.center = center this.title = title @@ -53,29 +53,7 @@ export class BaseChunk implements GameChunk { live() {} - #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({ game: this.game, theme, area }) - } - - public getRandomPoint() { + get randomPoint() { return { x: getRandomInRange(this.area.area.startX, this.area.area.endX), y: getRandomInRange(this.area.area.startY, this.area.area.endY), @@ -146,4 +124,25 @@ export class BaseChunk implements GameChunk { get wagonStop(): IGameBuildingWagonStop | undefined { return this.game.children.find((b) => b.type === 'WAGON_STOP') as IGameBuildingWagonStop | undefined } + + #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({ game: this.game, theme, area }) + this.area.init() + } } diff --git a/src/lib/game/services/chunk/forestChunk.ts b/src/lib/game/services/chunk/forestChunk.ts index 7534289c..442426ae 100644 --- a/src/lib/game/services/chunk/forestChunk.ts +++ b/src/lib/game/services/chunk/forestChunk.ts @@ -39,7 +39,7 @@ export class ForestChunk extends BaseChunk implements IGameForestChunk { #initTrees(count: number) { for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() + const point = this.randomPoint const size = getRandomInRange(75, 90) new TreeObject({ game: this.game, @@ -55,7 +55,7 @@ export class ForestChunk extends BaseChunk implements IGameForestChunk { #initStones(count: number) { for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() + const point = this.randomPoint new StoneObject({ game: this.game, x: point.x, diff --git a/src/lib/game/services/chunk/lakeChunk.ts b/src/lib/game/services/chunk/lakeChunk.ts index dd6f5c24..b61c23dd 100644 --- a/src/lib/game/services/chunk/lakeChunk.ts +++ b/src/lib/game/services/chunk/lakeChunk.ts @@ -11,7 +11,7 @@ import type { IGameLakeChunk, } from '$lib/game/services/chunk/interface' -interface ILakeOptions { +interface LakeChunkOptions { game: Game center: IGameLakeChunk['center'] width: number @@ -20,7 +20,7 @@ interface ILakeOptions { } export class LakeChunk extends BaseChunk implements IGameLakeChunk { - constructor({ game, width, height, center, theme }: ILakeOptions) { + constructor({ game, width, height, center, theme }: LakeChunkOptions) { super({ game, width, @@ -54,7 +54,7 @@ export class LakeChunk extends BaseChunk implements IGameLakeChunk { #initTrees(count: number) { for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() + const point = this.randomPoint const size = getRandomInRange(75, 90) new TreeObject({ game: this.game, @@ -70,7 +70,7 @@ export class LakeChunk extends BaseChunk implements IGameLakeChunk { #initStones(count: number) { for (let i = 0; i < count; i++) { - const point = this.getRandomPoint() + const point = this.randomPoint new StoneObject({ game: this.game, x: point.x, diff --git a/src/lib/game/services/chunk/villageChunk.ts b/src/lib/game/services/chunk/villageChunk.ts index 213603d8..bd017ad8 100644 --- a/src/lib/game/services/chunk/villageChunk.ts +++ b/src/lib/game/services/chunk/villageChunk.ts @@ -24,7 +24,7 @@ import type { IGameVillageChunk, } from '$lib/game/services/chunk/interface' -interface IVillageOptions { +interface VillageChunkOptions { game: Game width: number height: number @@ -33,19 +33,18 @@ interface IVillageOptions { } export class VillageChunk extends BaseChunk implements IGameVillageChunk { - constructor({ width, height, center, theme, game }: IVillageOptions) { + constructor({ width, height, center, theme, game }: VillageChunkOptions) { super({ title: '', type: 'VILLAGE', theme, width, height, center, game }) - this.title = this.getRandomTitle() + this.title = this.#getRandomTitle() - this.initFlags('RESOURCE', 80) + this.#initFlags('RESOURCE', 80) // this.initFlags("MOVEMENT", 30) - this.initTrees(20) - this.initStones(5) - - this.initCourier(1) - this.initFarmer(1) - this.initBuildings() + this.#initTrees(30) + this.#initStones(5) + this.#initCourier(1) + this.#initFarmer(1) + this.#initBuildings() } live() { @@ -64,6 +63,44 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { } } + checkIfNeedToPlantTree() { + const treesNow = this.game.children.filter( + (t) => t instanceof TreeObject && t.chunkId === this.id && t.state !== 'DESTROYED', + ) + if (treesNow.length < 40) { + return this.#getRandomEmptyResourceFlagInVillage() + } + } + + plantNewTree(flag: FlagObject) { + const tree = new TreeObject({ + game: this.game, + x: flag.x, + y: flag.y, + resource: 1, + size: 12, + health: 20, + theme: this.area.theme, + }) + + flag.target = tree + flag.isReserved = false + tree.init() + } + + getTreesAmount() { + return this.game.children.filter( + (obj) => obj instanceof TreeObject && obj.chunkId === this.id && obj.state !== 'DESTROYED', + ).length + } + + checkIfThereAreNotEnoughTrees() { + const max = this.#getResourceFlagInVillageAmount() + const now = this.getTreesAmount() + + return now < max / 3 + } + addTaskToCourier(object: Courier) { const random = getRandomInRange(1, 500) if (random !== 1) { @@ -83,7 +120,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { const buildFunc = (): boolean => { warehouse?.inventory.reduceOrDestroyItem('WOOD', 25) - this.buildStore() + this.#buildStore() return true } @@ -144,7 +181,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { return } - const target = this.getRandomMovementFlagInVillage() + const target = this.#getRandomMovementFlagInVillage() if (!target) { return } @@ -172,7 +209,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { // No Trees needed? const random = getRandomInRange(1, 300) if (random <= 1) { - const target = this.getRandomMovementFlagInVillage() + const target = this.#getRandomMovementFlagInVillage() if (!target) { return } @@ -183,8 +220,8 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { } } - initFlag(variant: GameObjectFlag['variant']) { - const randomPoint = this.getRandomPoint() + #initFlag(variant: GameObjectFlag['variant']) { + const randomPoint = this.randomPoint new FlagObject({ game: this.game, chunkId: this.id, @@ -194,13 +231,13 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { }).init() } - initFlags(variant: GameObjectFlag['variant'], count: number) { + #initFlags(variant: GameObjectFlag['variant'], count: number) { for (let i = 0; i < count; i++) { - this.initFlag(variant) + this.#initFlag(variant) } } - initTrees(count: number) { + #initTrees(count: number) { for (let i = 0; i < count; i++) { const flag = this.#getRandomEmptyResourceFlagInVillage() if (flag) { @@ -220,7 +257,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { } } - initStones(count: number) { + #initStones(count: number) { for (let i = 0; i < count; i++) { const flag = this.#getRandomEmptyResourceFlagInVillage() if (flag) { @@ -236,9 +273,9 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { } } - initCourier(count = 1) { + #initCourier(count = 1) { for (let i = 0; i < count; i++) { - const randomPoint = this.getRandomPoint() + const randomPoint = this.randomPoint new Courier({ game: this.game, x: randomPoint.x, @@ -247,9 +284,9 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { } } - initFarmer(count = 1) { + #initFarmer(count = 1) { for (let i = 0; i < count; i++) { - const randomPoint = this.getRandomPoint() + const randomPoint = this.randomPoint new Farmer({ game: this.game, x: randomPoint.x, @@ -258,7 +295,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { } } - initBuildings() { + #initBuildings() { new Campfire({ game: this.game, x: this.center.x, @@ -284,7 +321,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { }).init() } - buildStore() { + #buildStore() { const constructionArea = this.game.chunkService.chunk?.constructionArea if (!constructionArea) { return @@ -320,7 +357,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { ).length } - getRandomMovementFlagInVillage() { + #getRandomMovementFlagInVillage() { const flags = this.game.children.filter( (f) => f instanceof FlagObject && f.chunkId === this.id && f.variant === 'MOVEMENT', ) @@ -329,7 +366,7 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { : undefined } - getRandomTitle() { + #getRandomTitle() { const titles = [ 'Windy Peak', 'Green Grove', @@ -344,42 +381,4 @@ export class VillageChunk extends BaseChunk implements IGameVillageChunk { ] return titles[Math.floor(Math.random() * titles.length)] } - - checkIfNeedToPlantTree() { - const treesNow = this.game.children.filter( - (t) => t instanceof TreeObject && t.chunkId === this.id && t.state !== 'DESTROYED', - ) - if (treesNow.length < 40) { - return this.#getRandomEmptyResourceFlagInVillage() - } - } - - plantNewTree(flag: FlagObject) { - const tree = new TreeObject({ - game: this.game, - x: flag.x, - y: flag.y, - resource: 1, - size: 12, - health: 20, - theme: this.area.theme, - }) - - flag.target = tree - flag.isReserved = false - tree.init() - } - - getTreesAmount() { - return this.game.children.filter( - (obj) => obj instanceof TreeObject && obj.chunkId === this.id && obj.state !== 'DESTROYED', - ).length - } - - checkIfThereAreNotEnoughTrees() { - const max = this.#getResourceFlagInVillageAmount() - const now = this.getTreesAmount() - - return now < max / 3 - } } diff --git a/src/lib/game/services/wagonService.ts b/src/lib/game/services/wagonService.ts index 4fbf42ac..85631df7 100644 --- a/src/lib/game/services/wagonService.ts +++ b/src/lib/game/services/wagonService.ts @@ -24,6 +24,7 @@ export class WagonService implements GameWagonService { initWagon({ x, y }: { x: number, y: number }) { this.wagon = new BaseWagon({ game: this.game, x, y }) + this.wagon.init() this.#initOutFlags() this.#initNearFlags()