diff --git a/package.json b/package.json index b681649..4cce13b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@neutralinojs/lib": "^5.3.0", + "navmesh": "^2.3.1", "phaser": "^3.80.1", "phaser3-rex-plugins": "^1.80.6" }, diff --git a/src/assets/assets.ts b/src/assets/assets.ts index c27a5ea..8c6cfb9 100644 --- a/src/assets/assets.ts +++ b/src/assets/assets.ts @@ -42,8 +42,11 @@ const images: Image[] = [ // Items image('items/coin', 'coin'), image('items/bath1', 'bath_1'), + image('items/bath1f', 'bath_1_front'), image('items/bath2', 'bath_2'), + image('items/bath2f', 'bath_2_front'), image('items/bath3', 'bath_3'), + image('items/bath3f', 'bath_3_front'), image('items/wax1', 'wax_1'), image('items/wax2', 'wax_2'), image('items/wax3', 'wax_3'), @@ -65,6 +68,7 @@ const images: Image[] = [ image('ui/question', 'question'), image('ui/sad', 'sad'), image('ui/timer', 'timer'), + image('ui/plus', 'plus'), // Titlescreen image('titlescreen/sky', 'title_sky'), diff --git a/src/assets/images/backgrounds/grid2.png b/src/assets/images/backgrounds/grid2.png index 9a8a00b..47989e7 100644 Binary files a/src/assets/images/backgrounds/grid2.png and b/src/assets/images/backgrounds/grid2.png differ diff --git a/src/assets/images/items/bath1.png b/src/assets/images/items/bath1.png index 93e2548..259a762 100644 Binary files a/src/assets/images/items/bath1.png and b/src/assets/images/items/bath1.png differ diff --git a/src/assets/images/items/bath1f.png b/src/assets/images/items/bath1f.png new file mode 100644 index 0000000..c5aa927 Binary files /dev/null and b/src/assets/images/items/bath1f.png differ diff --git a/src/assets/images/items/bath2.png b/src/assets/images/items/bath2.png index 8c74450..0b366e3 100644 Binary files a/src/assets/images/items/bath2.png and b/src/assets/images/items/bath2.png differ diff --git a/src/assets/images/items/bath2f.png b/src/assets/images/items/bath2f.png new file mode 100644 index 0000000..d88283b Binary files /dev/null and b/src/assets/images/items/bath2f.png differ diff --git a/src/assets/images/items/bath3.png b/src/assets/images/items/bath3.png index b5f0a5d..6c4e0bd 100644 Binary files a/src/assets/images/items/bath3.png and b/src/assets/images/items/bath3.png differ diff --git a/src/assets/images/items/bath3f.png b/src/assets/images/items/bath3f.png new file mode 100644 index 0000000..bb4e594 Binary files /dev/null and b/src/assets/images/items/bath3f.png differ diff --git a/src/assets/images/items/nail1.png b/src/assets/images/items/nail1.png index 3aec638..dfa675a 100644 Binary files a/src/assets/images/items/nail1.png and b/src/assets/images/items/nail1.png differ diff --git a/src/assets/images/items/nail2.png b/src/assets/images/items/nail2.png index 785d1ca..2100ad1 100644 Binary files a/src/assets/images/items/nail2.png and b/src/assets/images/items/nail2.png differ diff --git a/src/assets/images/items/nail3.png b/src/assets/images/items/nail3.png index ad4bc25..0e87d1b 100644 Binary files a/src/assets/images/items/nail3.png and b/src/assets/images/items/nail3.png differ diff --git a/src/assets/images/items/wax1.png b/src/assets/images/items/wax1.png index 391a613..29317a1 100644 Binary files a/src/assets/images/items/wax1.png and b/src/assets/images/items/wax1.png differ diff --git a/src/assets/images/ui/plus.png b/src/assets/images/ui/plus.png new file mode 100644 index 0000000..74708d2 Binary files /dev/null and b/src/assets/images/ui/plus.png differ diff --git a/src/components/Board.ts b/src/components/Board.ts index 027b741..bba8dcd 100644 --- a/src/components/Board.ts +++ b/src/components/Board.ts @@ -1,8 +1,15 @@ import { GameScene } from "@/scenes/GameScene"; +export type GridPoint = { + gridX: number, + gridY: number +}; + export class Board extends Phaser.GameObjects.Container { public scene: GameScene; public size: number; + public width: number; + public height: number; private grid: Phaser.GameObjects.Grid; private things: any[]; @@ -21,6 +28,9 @@ export class Board extends Phaser.GameObjects.Container { // this.size = scene.H / (height + 2); this.size = cellSize; + this.width = width; + this.height = height + this.grid = this.scene.add.grid( 0, 0, @@ -44,6 +54,8 @@ export class Board extends Phaser.GameObjects.Container { resize(width: number, height: number, cellSize: number) { // this.size = this.scene.H / height; this.size = cellSize; + this.width = width; + this.height = height this.grid.destroy(); this.grid = this.scene.add.grid( @@ -70,7 +82,7 @@ export class Board extends Phaser.GameObjects.Container { } // Return grid cell of the coordinates - coordToGrid(x: number, y: number) { + coordToGrid(x: number, y: number): GridPoint { const gridX = Math.floor((x - this.x + this.grid.width / 2) / this.size); const gridY = Math.floor((y - this.y + this.grid.height / 2) / this.size); return { gridX, gridY }; diff --git a/src/components/Employee.ts b/src/components/Employee.ts index 1b812e9..e9516f4 100644 --- a/src/components/Employee.ts +++ b/src/components/Employee.ts @@ -5,6 +5,7 @@ import { EmployeeData, EmployeeId, EmployeeType } from "./EmployeeData"; export class Employee extends Button { public employeeId: EmployeeId; + public hasBeenPurchased: boolean; public currentCustomer: Customer | null; public doingCuteThing: boolean; @@ -33,6 +34,9 @@ export class Employee extends Button { this.startX = x; this.startY = y; + // Initially not purchased + this.hasBeenPurchased = false; + /* Sprite */ this.spriteCont = this.scene.add.container(0, this.spriteOffset); this.add(this.spriteCont); @@ -48,13 +52,13 @@ export class Employee extends Button { } update(time: number, delta: number) { - const factor = this.doingCuteThing ? 0.1 : 0.02; + const factor = this.doingCuteThing ? 0.1 : this.hasBeenPurchased ? 0.02 : 0; const squish = 1.0 + factor * Math.sin((6 * time) / 1000); this.spriteCont.setScale(1.0, squish - 0.2 * this.holdSmooth); } setCustomer(customer: Customer | null) { - if(customer){ + if (customer) { this.scene.sound.play("sqk"); } @@ -95,12 +99,25 @@ export class Employee extends Button { } upgrade() { - if (this.upgradeTo) { + // this.scene.sound.play("upgrade"); + + if (!this.hasBeenPurchased) { + this.hasBeenPurchased = true; + this.setAlpha(1.0); + } else if (this.upgradeTo) { this.employeeId = this.upgradeTo!; this.sprite.setTexture(this.spriteKey); } } + // Only used when loading levels + forceUpgrade(id: EmployeeId) { + this.hasBeenPurchased = true; + this.setAlpha(1.0); + this.employeeId = id; + this.sprite.setTexture(this.spriteKey); + } + /* Getters */ get employeeType(): EmployeeType { @@ -140,7 +157,13 @@ export class Employee extends Button { } get upgradeCost(): number { - return EmployeeData[this.employeeId].upgradeCost ?? 0; + if (!this.hasBeenPurchased) { + return EmployeeData[this.employeeId].cost; + } + if (this.upgradeTo) { + return EmployeeData[this.upgradeTo].cost; + } + return 0; } get upgradeTo(): EmployeeId | undefined { diff --git a/src/components/EmployeeData.ts b/src/components/EmployeeData.ts index c31ff2f..2546638 100644 --- a/src/components/EmployeeData.ts +++ b/src/components/EmployeeData.ts @@ -23,7 +23,7 @@ export interface EmployeeInterface { spriteKey: string; // Employee image key walkSpeed: number; // Speed of the employee walking workSpeed: number; // Speed of the employee working - upgradeCost?: number; // Cost to upgrade the employee + cost: number; // Cost to purchase that tier of employee upgradeTo?: EmployeeId; // Employee to upgrade to } @@ -35,7 +35,7 @@ export const EmployeeData: { [key in EmployeeId]: EmployeeInterface } = { spriteKey: "worker", walkSpeed: 1, workSpeed: 1, - upgradeCost: 100, + cost: 300, upgradeTo: EmployeeId.RaccoonTier2, }, [EmployeeId.RaccoonTier2]: { @@ -45,7 +45,7 @@ export const EmployeeData: { [key in EmployeeId]: EmployeeInterface } = { spriteKey: "worker", walkSpeed: 2, workSpeed: 2, - upgradeCost: 500, + cost: 400, upgradeTo: EmployeeId.RaccoonTier3, }, [EmployeeId.RaccoonTier3]: { @@ -55,6 +55,7 @@ export const EmployeeData: { [key in EmployeeId]: EmployeeInterface } = { spriteKey: "worker", walkSpeed: 3, workSpeed: 3, + cost: 800, }, [EmployeeId.HumanTier1]: { @@ -64,7 +65,7 @@ export const EmployeeData: { [key in EmployeeId]: EmployeeInterface } = { spriteKey: "player", walkSpeed: 1, workSpeed: 1, - upgradeCost: 100, + cost: 1000, upgradeTo: EmployeeId.HumanTier2, }, [EmployeeId.HumanTier2]: { @@ -74,7 +75,7 @@ export const EmployeeData: { [key in EmployeeId]: EmployeeInterface } = { spriteKey: "player", walkSpeed: 2, workSpeed: 2, - upgradeCost: 500, + cost: 400, upgradeTo: EmployeeId.HumanTier3, }, [EmployeeId.HumanTier3]: { @@ -84,5 +85,6 @@ export const EmployeeData: { [key in EmployeeId]: EmployeeInterface } = { spriteKey: "player", walkSpeed: 3, workSpeed: 3, + cost: 800, }, }; diff --git a/src/components/Levels.ts b/src/components/Levels.ts index ccae925..6ee0d1b 100644 --- a/src/components/Levels.ts +++ b/src/components/Levels.ts @@ -11,7 +11,7 @@ export interface Level { height: number; cellSize: number; grid: number[][]; - customerArrivalTimes: number[]; + upgradeCost?: number; } export enum BlockType { @@ -37,13 +37,13 @@ export const LevelData: Level[] = [ cellSize: 190, grid: [ [X, X, X, X, X, X, X, X], + [X, 2, _, 9, 9, _, 4, X], [X, 2, _, 3, 3, _, 4, X], - [X, 2, _, _, _, _, 4, X], - [_, _, _, 5, 5, _, _, X], - [X, 9, 9, _, _, 6, _, _], + [_, _, _, _, _, _, _, X], + [X, _, 5, 5, _, 6, _, _], [X, X, X, X, X, X, X, X], ], - customerArrivalTimes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90], + upgradeCost: 1000, }, { id: LevelId.Level2, @@ -60,17 +60,14 @@ export const LevelData: Level[] = [ [X, 9, 9, 9, _, _, 6, _, _], [X, X, X, X, X, X, X, X, X], ], - customerArrivalTimes: [ - 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, - 95, 100, - ], + upgradeCost: 2000, }, { id: LevelId.Level3, - background: "grid3", + background: "grid2", width: 8 + 2, height: 6 + 2, - cellSize: 109, + cellSize: 138, grid: [ [X, X, X, X, X, X, X, X, X, X], [X, 2, _, _, _, _, _, _, _, X], @@ -81,9 +78,5 @@ export const LevelData: Level[] = [ [X, 9, 9, 9, 9, _, _, 6, _, _], [X, X, X, X, X, X, X, X, X, X], ], - customerArrivalTimes: [ - 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, - 95, 100, - ], }, ]; diff --git a/src/components/Station.ts b/src/components/Station.ts index 8473e68..ca515b1 100644 --- a/src/components/Station.ts +++ b/src/components/Station.ts @@ -14,8 +14,8 @@ import { TextEffect } from "./TextEffect"; export class Station extends Button { public stationId: StationId; + public hasBeenPurchased: boolean; public currentCustomer: Customer | null; // The customer using the station - //public taskDuration: number; // Time it takes to complete a task public taskSpeed: number = 1; // For permanent bonuses //temp variables @@ -54,6 +54,9 @@ export class Station extends Button { this.cellSize = cellSize; this.currentCustomer = null; + // Initially not purchased + this.hasBeenPurchased = false; + /* Sprite */ this.spriteCont = this.scene.add.container(0, this.spriteOffset); this.add(this.spriteCont); @@ -109,7 +112,8 @@ export class Station extends Button { } update(time: number, delta: number) { - const squish = 1.0 + 0.02 * Math.sin((6 * time) / 1000); + const amount = this.hasBeenPurchased ? 0.02 : 0; + const squish = 1.0 + amount * Math.sin((6 * time) / 1000); this.spriteCont.setScale(1.0, squish - 0.2 * this.holdSmooth); } @@ -124,7 +128,7 @@ export class Station extends Button { ? (this.taskHaste *= this.currentCustomer.workMultiplier) : (this.taskHaste *= 1); this.parseItems(); - if(this.queueFail) { + if (this.queueFail) { this.scene.sound.play("rip"); } else { this.playJingle(); @@ -222,7 +226,12 @@ export class Station extends Button { } upgrade() { - if (this.upgradeTo) { + // this.scene.sound.play("upgrade"); + + if (!this.hasBeenPurchased) { + this.hasBeenPurchased = true; + this.setAlpha(1.0); + } else if (this.upgradeTo) { this.stationId = this.upgradeTo!; this.sprite.setTexture(this.spriteKey); this.spriteCont.y = this.spriteOffset; @@ -230,6 +239,16 @@ export class Station extends Button { } } + // Only used when loading levels + forceUpgrade(id: StationId) { + this.hasBeenPurchased = true; + this.setAlpha(1.0); + this.stationId = id; + this.sprite.setTexture(this.spriteKey); + this.spriteCont.y = this.spriteOffset; + this.sprite.setScale(this.spriteSize / this.sprite.width); + } + applyItem(id: number, sp: string) { this.appliedItems.push(id); let st = new Phaser.GameObjects.Sprite( @@ -280,15 +299,17 @@ export class Station extends Button { //this.scene.sound.play("meme_explosion_sound"); } - playJingle(){ - switch(this.stationType){ + playJingle() { + switch (this.stationType) { case StationType.ScalePolish: { this.scene.sound.play("polish"); break; - } case StationType.GoldBath: { + } + case StationType.GoldBath: { this.scene.sound.play("goldbath"); break; - } case StationType.HornAndNails: { + } + case StationType.HornAndNails: { this.scene.sound.play("snip"); break; } @@ -334,7 +355,13 @@ export class Station extends Button { } get upgradeCost(): number { - return StationData[this.stationId].upgradeCost ?? 0; + if (!this.hasBeenPurchased) { + return StationData[this.stationId].cost; + } + if (this.upgradeTo) { + return StationData[this.upgradeTo].cost; + } + return 0; } get upgradeTo(): StationId | undefined { diff --git a/src/components/StationData.ts b/src/components/StationData.ts index fa5a0b9..280b994 100644 --- a/src/components/StationData.ts +++ b/src/components/StationData.ts @@ -62,139 +62,144 @@ export interface StationInterface { spriteScale: number; // Multiplier for sprite size taskDuration?: number; // Time it takes to complete a task admissionFee?: number; // Customer pays this amount to use the station - upgradeCost?: number; // Cost to upgrade the station + cost: number; // Cost to purchase the station upgradeTo?: StationId; // Station to upgrade to } export const StationData: { [key in StationId]: StationInterface } = { [StationId.WaitingSeatTier1]: { type: StationType.WaitingSeat, - name: "WaitingSeat station", + name: "Waiting seat", tier: 1, spriteKey: "waitchair_1", spriteScale: 1.0, - upgradeCost: 100, + cost: 100, upgradeTo: StationId.WaitingSeatTier2, }, [StationId.WaitingSeatTier2]: { type: StationType.WaitingSeat, - name: "WaitingSeat station", + name: "Waiting armchair", tier: 2, spriteKey: "waitchair_2", spriteScale: 1.25, - upgradeCost: 500, + cost: 250, upgradeTo: StationId.WaitingSeatTier3, }, [StationId.WaitingSeatTier3]: { type: StationType.WaitingSeat, - name: "WaitingSeat station", + name: "Waiting throne", tier: 3, spriteKey: "waitchair_3", spriteScale: 1.5, + cost: 500, }, [StationId.HornAndNailsTier1]: { type: StationType.HornAndNails, - name: "HornAndNails station", + name: "Talonicure pillow", tier: 1, spriteKey: "nail_1", spriteScale: 1.0, taskDuration: 3000, admissionFee: 20, - upgradeCost: 100, + cost: 150, upgradeTo: StationId.HornAndNailsTier2, }, [StationId.HornAndNailsTier2]: { type: StationType.HornAndNails, - name: "HornAndNails station", + name: "Talonicure beanbag", tier: 2, spriteKey: "nail_2", spriteScale: 1.25, taskDuration: 2500, admissionFee: 40, - upgradeCost: 300, + cost: 250, upgradeTo: StationId.HornAndNailsTier3, }, [StationId.HornAndNailsTier3]: { type: StationType.HornAndNails, - name: "HornAndNails station", + name: "Talonicure bed", tier: 3, spriteKey: "nail_3", spriteScale: 1.5, taskDuration: 2000, admissionFee: 60, + cost: 400, }, [StationId.ScalePolishTier1]: { type: StationType.ScalePolish, - name: "ScalePolish station", + name: "Scalicure chair", tier: 1, spriteKey: "wax_1", spriteScale: 1.0, taskDuration: 2000, admissionFee: 10, - upgradeCost: 150, + cost: 100, upgradeTo: StationId.ScalePolishTier2, }, [StationId.ScalePolishTier2]: { type: StationType.ScalePolish, - name: "ScalePolish station", + name: "Scalicure station", tier: 2, spriteKey: "wax_2", spriteScale: 1.25, taskDuration: 1500, admissionFee: 20, - upgradeCost: 400, + cost: 250, upgradeTo: StationId.ScalePolishTier3, }, [StationId.ScalePolishTier3]: { type: StationType.ScalePolish, - name: "ScalePolish station", + name: "Scalicure divan", tier: 3, spriteKey: "wax_3", spriteScale: 1.5, taskDuration: 1000, admissionFee: 30, + cost: 500, }, [StationId.GoldBathTier1]: { type: StationType.GoldBath, - name: "GoldBath station", + name: "Draconic bath", tier: 1, spriteKey: "bath_1", spriteScale: 1.0, taskDuration: 4000, admissionFee: 20, - upgradeCost: 200, + cost: 200, upgradeTo: StationId.GoldBathTier2, }, [StationId.GoldBathTier2]: { type: StationType.GoldBath, - name: "GoldBath station", + name: "Draconic shower", tier: 2, spriteKey: "bath_2", spriteScale: 1.25, taskDuration: 3000, admissionFee: 30, - upgradeCost: 600, + cost: 350, upgradeTo: StationId.GoldBathTier3, }, [StationId.GoldBathTier3]: { type: StationType.GoldBath, - name: "GoldBath station", + name: "Dracuzzi", tier: 3, spriteKey: "bath_3", spriteScale: 1.5, taskDuration: 2000, admissionFee: 40, + cost: 600, }, [StationId.CashRegister]: { type: StationType.CashRegister, - name: "CashRegister station", + name: "Cash register", tier: 1, spriteKey: "checkout", spriteScale: 1.4, taskDuration: 500, + cost: 0, }, }; diff --git a/src/components/UI.ts b/src/components/UI.ts index 55f28db..11216a4 100644 --- a/src/components/UI.ts +++ b/src/components/UI.ts @@ -1,6 +1,7 @@ import { GameScene } from "@/scenes/GameScene"; import { Timer } from "./Timer"; import { TextButton } from "./TextButton"; +import { Level } from "./Levels"; export class UI extends Phaser.GameObjects.Container { public scene: GameScene; @@ -72,20 +73,23 @@ export class UI extends Phaser.GameObjects.Container { this.moneyText.setOrigin(0.5); this.panel.add(this.moneyText); - this.nextButton = new TextButton(scene, 0, 300, 240, 80, "Next day"); + this.nextButton = new TextButton(scene, 0, 600, 300, 80, "Start day"); this.panel.add(this.nextButton); this.nextButton.on("click", () => { this.emit("nextDay"); }); - this.newLocationButton = new TextButton(scene, 0, 440, 240, 80, "Move"); + this.newLocationButton = new TextButton(scene, 0, 400, 300, 200, "..."); this.panel.add(this.newLocationButton); this.newLocationButton.on("click", () => { this.emit("nextLevel"); }); } - update(time: number, delta: number) {} + update(time: number, delta: number) { + this.nextButton.update(time, delta); + this.newLocationButton.update(time, delta); + } setDay(day: number) { this.dayText.setText(`Day ${day}`); @@ -95,8 +99,21 @@ export class UI extends Phaser.GameObjects.Container { this.dayProgressTimer.redraw(time); } + setLevel(level: Level) { + this.newLocationButton.setData("cost", level.upgradeCost); + this.newLocationButton.setVisible(level.upgradeCost !== undefined); + this.newLocationButton.setText( + `Upgrade\n shop\n $${level.upgradeCost}` + ); + } + setMoney(money: number) { this.moneyText.setText(`Money: $${money}`); + + const upgradeCost = this.newLocationButton.getData("cost"); + const canUpgrade = money >= upgradeCost; + this.newLocationButton.setAlpha(canUpgrade ? 1 : 0.5); + this.newLocationButton.enabled = canUpgrade; } setShoppingMode(isShopping: boolean) { diff --git a/src/components/UpgradeOverlay.ts b/src/components/UpgradeOverlay.ts index 5d99326..4c937d6 100644 --- a/src/components/UpgradeOverlay.ts +++ b/src/components/UpgradeOverlay.ts @@ -102,7 +102,7 @@ export class UpgradeOverlay extends Phaser.GameObjects.Container { } // Upgrade text if there is an upgrade available - if (station.upgradeTo) { + if (station.upgradeTo && station.hasBeenPurchased) { const nextData = StationData[station.upgradeTo]; const durationDiff = nextData.taskDuration! - station.taskDuration; const revenueDiff = nextData.admissionFee! - station.admissionFee; @@ -118,9 +118,6 @@ export class UpgradeOverlay extends Phaser.GameObjects.Container { text += `Revenue: $${station.admissionFee} (+$${revenueDiff})\n`; } this.moneyText.setText(text); - - this.buyButton.setVisible(true); - this.buyButton.setText(`$${station.upgradeCost}`); } // Otherwise, show current stats else { @@ -129,13 +126,13 @@ export class UpgradeOverlay extends Phaser.GameObjects.Container { text += `Duration: ${station.taskDuration / 1000}s\n`; text += `Revenue: $${station.admissionFee}\n`; this.moneyText.setText(text); - - this.buyButton.setVisible(false); } // Enable/disable buy button const canAfford = station.upgradeCost <= this.scene.money; this.buyButton.setEnabled(canAfford); + this.buyButton.setVisible(!!station.upgradeTo || !station.hasBeenPurchased); + this.buyButton.setText(`$${station.upgradeCost}`); this.open(); } @@ -155,7 +152,7 @@ export class UpgradeOverlay extends Phaser.GameObjects.Container { } // Upgrade text if there is an upgrade available - if (employee.upgradeTo) { + if (employee.upgradeTo && employee.hasBeenPurchased) { const nextData = EmployeeData[employee.upgradeTo]; const walkDiff = nextData.walkSpeed! - employee.walkSpeed; const workDiff = nextData.workSpeed! - employee.workSpeed; @@ -167,7 +164,6 @@ export class UpgradeOverlay extends Phaser.GameObjects.Container { this.moneyText.setText(text); this.buyButton.setVisible(true); - this.buyButton.setText(`$${employee.upgradeCost}`); } // Otherwise, show current stats else { @@ -176,13 +172,15 @@ export class UpgradeOverlay extends Phaser.GameObjects.Container { text += `Walk speed: ${employee.walkSpeed}\n`; text += `Work speed: ${employee.workSpeed}\n`; this.moneyText.setText(text); - - this.buyButton.setVisible(false); } // Enable/disable buy button const canAfford = employee.upgradeCost <= this.scene.money; this.buyButton.setEnabled(canAfford); + this.buyButton.setVisible( + !!employee.upgradeTo || !employee.hasBeenPurchased + ); + this.buyButton.setText(`$${employee.upgradeCost}`); this.open(); } diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index eaff624..b682ba7 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -1,11 +1,11 @@ import { BaseScene } from "@/scenes/BaseScene"; -import { Board } from "@/components/Board"; +import { Board, GridPoint } from "@/components/Board"; import { Employee } from "@/components/Employee"; import { Customer } from "@/components/Customer"; import { CustomerId } from "@/components/CustomerData"; import { Station } from "@/components/Station"; import { UI } from "@/components/UI"; -import { StationId, StationType } from "@/components/StationData"; +import { StationData, StationId, StationType } from "@/components/StationData"; import { Inventory } from "@/components/Inventory"; import { SimpleButton } from "@/components/elements/SimpleButton"; import { ToggleButton } from "@/components/elements/ToggleButton"; @@ -13,7 +13,7 @@ import { ItemButton } from "@/components/ItemButton"; import { ItemHandler } from "@/components/ItemHandler"; import { UpgradeOverlay } from "@/components/UpgradeOverlay"; import { SummaryOverlay } from "@/components/SummaryOverlay"; -import { EmployeeId } from "@/components/EmployeeData"; +import { EmployeeData, EmployeeId } from "@/components/EmployeeData"; import { BlockType, LevelData, LevelId } from "@/components/Levels"; import { Effect } from "@/components/Effect"; import { TextEffect } from "@/components/TextEffect"; @@ -21,6 +21,9 @@ import { BasicEffect } from "@/components/BasicEffect"; import { Intermission, Mode } from "@/components/Intermission"; import { SnapType } from "@/components/Item"; +import { NavMesh } from "navmesh"; +import { centerOnSubdividedCoord, GenerateNavMesh } from "@/utils/NavMeshHelper"; + enum GameState { Cutscene, Day, @@ -28,6 +31,7 @@ enum GameState { Intermission, } + export class GameScene extends BaseScene { private background: Phaser.GameObjects.Image; private board: Board; @@ -46,6 +50,7 @@ export class GameScene extends BaseScene { public activeItem: ItemButton; public effects: Effect[]; + private navmesh: NavMesh; // Game stats public state: GameState = GameState.Cutscene; @@ -53,13 +58,21 @@ export class GameScene extends BaseScene { public day: number = 0; public dayDuration: number = 60000; // 1 minute public timeOfDay: number = 0; - public money: number = 100000; + public customerSpawnTimer: Phaser.Time.TimerEvent; + public customerSpawnPool: CustomerId[] = []; + public money: number = 500; public dailyStats: { money: number; happyCustomers: number; angryCustomers: number; }; + // Keeps track of made purchases when starting a level + public savedPurchases: { + stations: StationId[]; + employees: EmployeeId[]; + }; + constructor() { super({ key: "GameScene" }); } @@ -73,6 +86,15 @@ export class GameScene extends BaseScene { // Reset daily stats this.dailyStats = { money: 0, happyCustomers: 0, angryCustomers: 0 }; + this.savedPurchases = { + stations: [ + StationId.WaitingSeatTier1, + StationId.HornAndNailsTier1, + StationId.ScalePolishTier1, + StationId.CashRegister, + ], + employees: [EmployeeId.RaccoonTier1], + }; // Background this.background = this.add.image(0, 0, "grid1"); @@ -87,13 +109,24 @@ export class GameScene extends BaseScene { this.ui = new UI(this); this.ui.setDepth(1000); + this.ui.on("nextDay", () => { + this.startDay(); + }); + this.ui.on("nextLevel", () => { + const upgradeCost = LevelData[this.level].upgradeCost ?? 0; + if (this.money >= upgradeCost) { + this.money -= upgradeCost; + this.ui.setMoney(this.money); + this.intermission.fadeToIntermission(Mode.NextLevelCutscene); + } + }); + this.iHandler = new ItemHandler(this); this.intermission = new Intermission(this); this.intermission.setDepth(10000); this.intermission.on("close", () => { this.intermission.fadeToGame(); - // this.startLevel(levels[this.levelIndex]); }); this.intermission.on("nextLevel", () => { const nextLevel = { @@ -135,16 +168,6 @@ export class GameScene extends BaseScene { SnapType.STATION ); - //UI - this.ui.setMoney(this.money); - this.ui.setDay(this.day); - this.ui.on("nextDay", () => { - this.startDay(); - }); - this.ui.on("nextLevel", () => { - this.intermission.fadeToIntermission(Mode.NextLevelCutscene); - }); - this.upgradeOverlay = new UpgradeOverlay(this); this.upgradeOverlay.setDepth(1010); this.upgradeOverlay.on("upgradeStation", (station: Station) => { @@ -152,12 +175,14 @@ export class GameScene extends BaseScene { this.ui.setMoney(this.money); station.upgrade(); this.upgradeOverlay.selectStation(station); + this.updateSavedPurchases(); }); this.upgradeOverlay.on("upgradeEmployee", (employee: Employee) => { this.money -= employee.upgradeCost; this.ui.setMoney(this.money); employee.upgrade(); this.upgradeOverlay.selectEmployee(employee); + this.updateSavedPurchases(); }); this.upgradeOverlay.on("close", () => { this.sortDepth(); @@ -171,37 +196,10 @@ export class GameScene extends BaseScene { /* Init */ - // TEMPORARY: Spawn customers every 5 seconds, if allowed - this.time.addEvent({ - delay: 5000, - callback: () => { - // Spawn new customer if shop is still open - if ( - this.state == GameState.Day && - this.timeOfDay > 0 && - this.getAvailableWaitingSeat() - ) { - const type = Phaser.Math.RND.pick([ - CustomerId.Small, - CustomerId.Medium, - CustomerId.Large, - // CustomerId.TypeA, - // CustomerId.TypeB, - // CustomerId.TypeC, - // CustomerId.TypeD, - // CustomerId.TypeE, - // CustomerId.TypeF, - ]); - this.addCustomer(type); - } - }, - loop: true, - }); - this.loadLevel(LevelId.Level1); this.setState(GameState.Shopping); // this.startDay(); - // this.intermission.fadeToGame(); + this.intermission.fadeToGame(); // Comment this out to see cutscenes } update(time: number, delta: number) { @@ -250,11 +248,22 @@ export class GameScene extends BaseScene { this.state = state; const isShopping = state === GameState.Shopping; - - this.stations.forEach((s) => s.setClickable(isShopping)); - this.employees.forEach((e) => e.setClickable(isShopping)); this.ui.setShoppingMode(isShopping); - if (isShopping && this.day > 0) this.summaryOverlay.open(this.dailyStats); + + // Make unpurchased objects are visible only during shopping + const unpurchasedAlpha = 0.2; + this.stations.forEach((s) => { + s.setClickable(isShopping); + s.setAlpha(s.hasBeenPurchased ? 1 : isShopping ? unpurchasedAlpha : 0); + }); + this.employees.forEach((e) => { + e.setClickable(isShopping); + e.setAlpha(e.hasBeenPurchased ? 1 : isShopping ? unpurchasedAlpha : 0); + }); + + if (isShopping && this.day > 0) { + this.summaryOverlay.open(this.dailyStats); + } } // Load level data @@ -306,6 +315,33 @@ export class GameScene extends BaseScene { } } } + + // Load saved purchases + this.savedPurchases.stations.forEach((id) => { + const station = this.stations.find( + (s) => !s.hasBeenPurchased && s.stationType === StationData[id].type + ); + if (station) { + station.forceUpgrade(id); + } + }); + this.savedPurchases.employees.forEach((id) => { + const employee = this.employees.find( + (e) => !e.hasBeenPurchased && e.employeeType === EmployeeData[id].type + ); + if (employee) { + employee.forceUpgrade(id); + } + }); + + // Generate navmesh + this.navmesh = GenerateNavMesh(this.board, LevelData[id]); + + this.ui.setLevel(level); + this.ui.setMoney(this.money); + this.ui.setDay(this.day); + + this.setState(GameState.Shopping); } // Start a new day @@ -321,17 +357,21 @@ export class GameScene extends BaseScene { this.stations.forEach((s) => s.setDepth(0)); this.employees.forEach((e) => e.setDepth(0)); - // TEMP: Add first customer - this.addCustomer(CustomerId.Small); + // Reset customer spawning + if (this.customerSpawnTimer) this.customerSpawnTimer.destroy(); + this.updateSpawnPool(); // Setup daytime tween this.tweens.add({ targets: this, - timeOfDay: { from: 1, to: 0 }, duration: this.dayDuration, + timeOfDay: { from: 0, to: 100 }, + + onStart: () => { + this.attemptSpawnCustomer(); + }, onUpdate: (tween) => { - this.timeOfDay = tween.getValue(); - this.ui.setTimeOfDay(this.timeOfDay); + this.ui.setTimeOfDay(1 - this.timeOfDay / 100); }, onComplete: () => { // Shop closed. Play sound. @@ -340,12 +380,77 @@ export class GameScene extends BaseScene { } endDay() { + this.customerSpawnTimer.destroy(); + //this.stations.forEach((s) => s.returnItems()); this.sound.play("endday"); this.employees.forEach((e) => e.walkTo(e.startX, e.startY)); this.setState(GameState.Shopping); } + // Attempt to spawn customer + canSpawnCustomer(id: CustomerId) { + return ( + this.state == GameState.Day && + this.timeOfDay < 100 && + this.getAvailableWaitingSeat(id) + ); + } + + // Attempt to spawn customer and reset timer + attemptSpawnCustomer() { + // Delay to next customer spawn + let delay = 2000; + + // Randomly select customer type + const id = Phaser.Math.RND.pick(this.customerSpawnPool); + + if (this.canSpawnCustomer(id)) { + this.addCustomer(id); + + // TODO: Adjust to difficulty + let delayMin = Math.max(1000, 5000 - 500 * this.day); + let delayMax = delayMin + 4000 - 500 * this.day; + delay = Phaser.Math.Between(delayMin, delayMax); + + console.log(`Customer spawned. Waiting ${delay} ms`); + } else { + console.log(`Customer failed to spawn. Waiting ${delay} ms`); + } + + // Setup new event timer + this.customerSpawnTimer = this.time.addEvent({ + delay, + callback: this.attemptSpawnCustomer, + callbackScope: this, + }); + } + + // Update customer spawn pool based on available stations + updateSpawnPool() { + this.customerSpawnPool = []; + + const tier2StationCount = this.stations.filter( + (s) => s.stationTier >= 2 && s.hasBeenPurchased + ).length; + const tier3StationCount = this.stations.filter( + (s) => s.stationTier >= 2 && s.hasBeenPurchased + ).length; + + this.customerSpawnPool.push(CustomerId.Small); + if (tier2StationCount >= 2) this.customerSpawnPool.push(CustomerId.Medium); + if (tier3StationCount >= 2) this.customerSpawnPool.push(CustomerId.Large); + } + + updateSavedPurchases() { + this.savedPurchases.stations = this.stations + .filter((s) => s.hasBeenPurchased) + .map((s) => s.stationId); + this.savedPurchases.employees = this.employees + .filter((e) => e.hasBeenPurchased) + .map((e) => e.employeeId); + } + // Add new station addStation(gridX: number, gridY: number, id: StationId) { const coord = this.board.gridToCoord(gridX, gridY); @@ -422,19 +527,13 @@ export class GameScene extends BaseScene { } // Add new customer - addCustomer(type: CustomerId) { + addCustomer(id: CustomerId) { const coord = this.board.gridToCoord(-8, 0); - const customer = new Customer( - this, - coord.x, - coord.y, - type, - this.board.size - ); + const customer = new Customer(this, coord.x, coord.y, id, this.board.size); this.customers.push(customer); // Place in available waiting seat - const seat = this.getAvailableWaitingSeat(); + const seat = this.getAvailableWaitingSeat(id); if (seat) { seat.setCustomer(customer); customer.setStation(seat); @@ -500,8 +599,6 @@ export class GameScene extends BaseScene { }); */ - - // Customer leaving the game customer.on("offscreen", () => { this.customers = this.customers.filter((c) => c !== customer); @@ -534,9 +631,13 @@ export class GameScene extends BaseScene { } // Get available seat for new customers to go to - getAvailableWaitingSeat() { + getAvailableWaitingSeat(id: CustomerId) { + // TODO: Use id to ensure seat and stations are available for tier return this.stations.find( - (s) => s.stationType === StationType.WaitingSeat && !s.currentCustomer + (s) => + s.stationType === StationType.WaitingSeat && + s.hasBeenPurchased && + !s.currentCustomer ); } @@ -554,6 +655,7 @@ export class GameScene extends BaseScene { station.y ); if ( + station.hasBeenPurchased && !station.currentCustomer && distance < closestDistance && distance < maxDistance && @@ -573,7 +675,7 @@ export class GameScene extends BaseScene { return; } - let closestEmployee: any = null; + let closestEmployee: Employee = null as unknown as Employee; let closestDistance = Infinity; this.employees.forEach((employee) => { @@ -583,7 +685,11 @@ export class GameScene extends BaseScene { employee.x, employee.y ); - if (!employee.currentCustomer && distance < closestDistance) { + if ( + employee.hasBeenPurchased && + !employee.currentCustomer && + distance < closestDistance + ) { closestEmployee = employee; closestDistance = distance; } @@ -599,29 +705,58 @@ export class GameScene extends BaseScene { closestEmployee.setCustomer(customer); closestEmployee.walkTo(x, y); + + const [cx, cy] = centerOnSubdividedCoord(this.board, station, 7); + const posEmp = this.board.coordToGrid(closestEmployee.x, closestEmployee.y); + const posSta = this.board.coordToGrid(station.x, station.y); + + + const scale = ({gridX, gridY}: GridPoint) => { + return {x: gridX*7, y: gridY*7} + } + + //this.navmesh.findPath({}) + + console.log(scale(posEmp)) + + const path = this.navmesh.findPath(scale(posEmp), scale(posSta)); + + + + console.log("path", path) + // Wait for employee.on("walkend") } } // Generate a list of requests for the customer setCustomerItinerary(customer: Customer) { + // Check availibility of stations + const check = (type: StationType) => { + return this.stations.some( + (s) => s.stationType === type && s.hasBeenPurchased + ); + }; + const hornNailsAvailable = check(StationType.HornAndNails); + const scalePolishAvailable = check(StationType.ScalePolish); + const goldBathAvailable = check(StationType.GoldBath); + function getActivities() { let activities = []; - if (Math.random() < 0.5) { + if (hornNailsAvailable && Math.random() < 0.6) { activities.push(StationType.HornAndNails); } - if (Math.random() < 0.5) { + if (scalePolishAvailable && Math.random() < 0.6) { activities.push(StationType.ScalePolish); } - if (Math.random() < 0.5) { + if (goldBathAvailable && Math.random() < 0.6) { activities.push(StationType.GoldBath); } - //activities.push(StationType.CashRegister); return activities; } let activities: StationType[] = []; - while (activities.length < 2) { + while (activities.length < 1) { activities = getActivities(); } diff --git a/src/utils/NavMeshHelper.ts b/src/utils/NavMeshHelper.ts new file mode 100644 index 0000000..6f777b1 --- /dev/null +++ b/src/utils/NavMeshHelper.ts @@ -0,0 +1,92 @@ +import type { Board } from "@/components/Board"; +import { Level, BlockType } from "@/components/Levels"; +import { StationId } from "@/components/StationData"; +import type { Station } from "@/components/Station"; +import NavMesh, { buildPolysFromGridMap } from "navmesh"; + +const subdivision = 7; + +const stationMask = [ + [true,true,true,true,true,true,true], + [true,true,false,false,false,false,true], + [true,false,false,false,false,true,true], + [true,true,false,true,true,true,true], + [true,false,false,false,false,true,true], + [true,true,false,false,false,false,true], + [true,true,true,true,true,true,true], +]; + +function stationToGrid(board: Board, station: Station) { + const {gridX, gridY} = board.coordToGrid(station.x, station.y); + return [gridX, gridY]; +} + +function Array2DFromGrid(board: Board, subdivision: number): boolean[][] { + console.log(board.height) + return new Array(board.width*subdivision).fill(true).map(() => new Array(board.height*subdivision).fill(true)); +} + +export function centerOnSubdividedCoord(board: Board, station: Station, subdivision: number) { + const [x, y] = stationToGrid(board, station); + const center = Math.floor(subdivision/2); + return [x+center, y+center]; +} + +const t = true; +const f = false; + +function testNav() { + + const mesh = [ + [f, f, f, f, f], + [f, t, t, t, f], + [f, t, f, t, f], + [f, t, t, t, f], + [f, t, t, t, f], + [f, f, f, f, f], + ]; + + const polys = new NavMesh(buildPolysFromGridMap(mesh)); + const path = polys.findPath( + {x: 1, y: 1}, + {x: 3, y: 3} + ) + console.log("new polys", ) + +} + +export function GenerateNavMesh(board: Board, level: Level) { + testNav() + const nav = Array2DFromGrid(board, subdivision); + + for (let y = 0; y < level.height; y++) { + for (let x = 0; x < level.width; x++) { + const block = level.grid[y][x]; + + switch (block) { + case BlockType.Wall: + for(let sx = 0; sx < stationMask.length; sx++) { + for(let sy = 0; sy < stationMask.length; sy++) { + nav[x*subdivision + sx][y*subdivision + sy] = false; + } + } + break; + case BlockType.HornAndNails: + case BlockType.ScalePolish: + case BlockType.GoldBath: + for(let sx = 0; sx < stationMask.length; sx++) { + for(let sy = 0; sy < stationMask.length; sy++) { + if(!stationMask[sx][sy]) { + nav[x*subdivision + sx][y*subdivision + sy] = false; + } + } + } + //case BlockType.CashRegister: + //this.addStation(x, y, StationId.CashRegister); + break; + } + } + } + console.log("nav", nav) + return new NavMesh(buildPolysFromGridMap(nav)); +} \ No newline at end of file