Compare commits
2 Commits
07d03befa2
...
1e4be0ff2c
| Author | SHA1 | Date |
|---|---|---|
|
|
1e4be0ff2c | |
|
|
b3574a8acf |
|
|
@ -48,6 +48,7 @@ export function* zip(...args: Iterable<any>[]) {
|
||||||
|
|
||||||
export const range = (size: number | string) => Object.keys((new Array(+size)).fill(0)).map(k => +k);
|
export const range = (size: number | string) => Object.keys((new Array(+size)).fill(0)).map(k => +k);
|
||||||
export const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
export const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
||||||
|
export const lerp = (start: number, end: number, t: number) => (start + (end - start) * t);
|
||||||
|
|
||||||
export const prevent = (e: Event) => (e.preventDefault(), false);
|
export const prevent = (e: Event) => (e.preventDefault(), false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,51 @@
|
||||||
import Item, { ItemType, type ItemTypeImage } from "./item";
|
import { lerp } from "@common/utils";
|
||||||
import { SpinnerAction } from "./spinner";
|
import Item from "./item";
|
||||||
import Tile from "./tile";
|
import Tile from "./tile";
|
||||||
|
|
||||||
|
const MOVE_DURATION = .1;
|
||||||
|
|
||||||
export default class Character extends Item {
|
export default class Character extends Item {
|
||||||
public health: number;
|
public tile = new Tile([0, 0], 1);
|
||||||
public inventory: Item[] = [];
|
private path: Tile[] = [];
|
||||||
public tile: Tile = new Tile([0, 0], 1);
|
private pathProgress = 0;
|
||||||
public lastDoor: Tile | undefined;
|
|
||||||
|
|
||||||
constructor(type: ItemTypeImage) {
|
public get displayPosition(): [number, number] {
|
||||||
super(type);
|
if (this.path.length > 1) {
|
||||||
this.health = this.maxHealth;
|
const numTransitions = this.path.length - 1;
|
||||||
const defaultItem = this.defaultItem;
|
const progress = this.pathProgress * numTransitions;
|
||||||
if (defaultItem) {
|
const currentTileIdx = Math.floor(progress);
|
||||||
this.inventory.push(this.defaultItem);
|
|
||||||
|
const currentTile = this.path[currentTileIdx];
|
||||||
|
const nextTile = this.path[currentTileIdx + 1];
|
||||||
|
|
||||||
|
if (!nextTile) {
|
||||||
|
return [currentTile.centerX, currentTile.centerY];
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionFraction = progress - currentTileIdx;
|
||||||
|
|
||||||
|
return [
|
||||||
|
lerp(currentTile.centerX, nextTile.centerX, transitionFraction),
|
||||||
|
lerp(currentTile.centerY, nextTile.centerY, transitionFraction),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return [this.tile.centerX, this.tile.centerY];
|
||||||
|
}
|
||||||
|
|
||||||
|
public moveTo(tile: Tile, path: Tile[]) {
|
||||||
|
this.pathProgress = 0;
|
||||||
|
this.tile = tile;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(dt: number) {
|
||||||
|
if (this.path.length > 1) {
|
||||||
|
if (this.pathProgress >= 1) {
|
||||||
|
this.path = [];
|
||||||
|
this.pathProgress = 0;
|
||||||
|
} else {
|
||||||
|
this.pathProgress += dt / (MOVE_DURATION * (this.path.length - 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
get maxHealth() {
|
|
||||||
return this.type === ItemType.CHAR_BIG ? 7 : 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultItem() {
|
|
||||||
if (this.type === ItemType.CHAR_NINJA) {
|
|
||||||
return new Item(ItemType.WEAPON_KNIFE);
|
|
||||||
}
|
|
||||||
if (this.type === ItemType.CHAR_POLICE) {
|
|
||||||
return new Item(ItemType.WEAPON_PISTOL);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get healingAmount() {
|
|
||||||
return this.type === ItemType.CHAR_NURSE ? 2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get moveBonus() {
|
|
||||||
return this.type === ItemType.CHAR_RUNNER ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isDead() {
|
|
||||||
return this.health <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get meleeWeapon() {
|
|
||||||
return this.inventory.find(i => i.isMeleeWeapon);
|
|
||||||
}
|
|
||||||
|
|
||||||
get gun() {
|
|
||||||
return this.inventory.find(i => i.isShootingWeapon);
|
|
||||||
}
|
|
||||||
|
|
||||||
get grenade() {
|
|
||||||
return this.inventory.find(i => i.type === ItemType.WEAPON_GRENADE);
|
|
||||||
}
|
|
||||||
|
|
||||||
get rocketLauncher() {
|
|
||||||
return this.inventory.find(i => i.type === ItemType.WEAPON_ROCKET_LAUNCHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public heal(character: Character) {
|
|
||||||
character.health += this.healingAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeItem(item: Item | null | undefined): boolean {
|
|
||||||
if (!item) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const itemIndex = this.inventory.findIndex(i => i === item);
|
|
||||||
|
|
||||||
if (itemIndex >= 0) {
|
|
||||||
this.inventory.splice(itemIndex, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public hasItem(item: Item | null | undefined): boolean {
|
|
||||||
if (!item) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const itemIndex = this.inventory.findIndex(i => i === item);
|
|
||||||
|
|
||||||
return itemIndex >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns true, if action was performed */
|
|
||||||
public handleSpin(action: SpinnerAction): boolean {
|
|
||||||
switch (action) {
|
|
||||||
case SpinnerAction.RUN:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SpinnerAction.BITE:
|
|
||||||
this.health -= 1;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SpinnerAction.MELEE:
|
|
||||||
return this.meleeWeapon != null && !this.tile.enemy?.isBoss;
|
|
||||||
|
|
||||||
case SpinnerAction.SHOOT:
|
|
||||||
return this.removeItem(this.gun) && !this.tile.enemy?.isBoss;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Characters = {
|
|
||||||
BIG: new Character(ItemType.CHAR_BIG),
|
|
||||||
NINJA: new Character(ItemType.CHAR_NINJA),
|
|
||||||
NURSE: new Character(ItemType.CHAR_NURSE),
|
|
||||||
POLICE: new Character(ItemType.CHAR_POLICE),
|
|
||||||
RUNNER: new Character(ItemType.CHAR_RUNNER),
|
|
||||||
} as const;
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
export default abstract class Entity {
|
export default abstract class Entity {
|
||||||
public hovered = false;
|
public hovered = false;
|
||||||
|
|
||||||
constructor(public position: [number, number], public size: [number, number]) {
|
constructor(public position: [number, number], public size: [number, number]) { }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(ctx: CanvasRenderingContext2D) {
|
public render(ctx: CanvasRenderingContext2D) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { range } from "@common/utils";
|
import { range } from "@common/utils";
|
||||||
import type Character from "./character";
|
import type Player from "./player";
|
||||||
import { Characters } from "./character";
|
import { Players } from "./player";
|
||||||
import Entity from "./entity";
|
import Entity from "./entity";
|
||||||
import Tile from "./tile";
|
import Tile from "./tile";
|
||||||
import type Item from "./item";
|
import type Item from "./item";
|
||||||
import type TileMap from "./tilemap";
|
import type TileMap from "./tilemap";
|
||||||
|
|
||||||
export type UseListener = (character: Character, item: Item) => void;
|
export type UseListener = (player: Player, item: Item) => void;
|
||||||
|
|
||||||
export default class Inventory extends Entity {
|
export default class Inventory extends Entity {
|
||||||
private tiles: Tile[][];
|
private tiles: Tile[][];
|
||||||
|
|
@ -14,26 +14,26 @@ export default class Inventory extends Entity {
|
||||||
constructor(public readonly map: TileMap, position: [number, number], size: [number, number]) {
|
constructor(public readonly map: TileMap, position: [number, number], size: [number, number]) {
|
||||||
super(position, size);
|
super(position, size);
|
||||||
|
|
||||||
const numCharacters = Object.keys(Characters).length;
|
const numPlayers = Object.keys(Players).length;
|
||||||
const tileSize = this.width / numCharacters;
|
const tileSize = this.width / numPlayers;
|
||||||
const numTiles = Math.floor(this.height / tileSize) - 2;
|
const numTiles = Math.floor(this.height / tileSize) - 2;
|
||||||
|
|
||||||
this.tiles = range(numCharacters).map((x) =>
|
this.tiles = range(numPlayers).map((x) =>
|
||||||
range(numTiles).map((y) =>
|
range(numTiles).map((y) =>
|
||||||
new Tile([x * tileSize, (2 + y) * tileSize], tileSize)
|
new Tile([x * tileSize, (2 + y) * tileSize], tileSize)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get characters() {
|
private get players() {
|
||||||
return this.map.characters;
|
return this.map.players;
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawCharacter(ctx: CanvasRenderingContext2D, idx: number) {
|
private drawPlayer(ctx: CanvasRenderingContext2D, idx: number) {
|
||||||
const character = this.characters[idx];
|
const player = this.players[idx];
|
||||||
ctx.drawImage(character.type, 0.1, 0.1, 0.8, 0.8);
|
ctx.drawImage(player.type, 0.1, 0.1, 0.8, 0.8);
|
||||||
ctx.fillText(`💖 ${character.health}`, 0.5, 1.5);
|
ctx.fillText(`💖 ${player.health}`, 0.5, 1.5);
|
||||||
if (character === this.map.character) {
|
if (player === this.map.player) {
|
||||||
ctx.strokeStyle = 'black';
|
ctx.strokeStyle = 'black';
|
||||||
ctx.lineWidth = 0.03;
|
ctx.lineWidth = 0.03;
|
||||||
|
|
||||||
|
|
@ -41,16 +41,16 @@ export default class Inventory extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
let y = 2;
|
let y = 2;
|
||||||
for (const item of character.inventory) {
|
for (const item of player.inventory) {
|
||||||
ctx.drawImage(item.type, 0.1, 0.1 + y, 0.8, 0.8);
|
ctx.drawImage(item.type, 0.1, 0.1 + y, 0.8, 0.8);
|
||||||
y += 1;
|
y += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override handleClick(x: number, y: number): void {
|
public override handleClick(x: number, y: number): void {
|
||||||
for (const { tile, item, character } of this.activeTiles) {
|
for (const { tile, item, player } of this.activeTiles) {
|
||||||
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
||||||
this.map.handleItemUse(character, item);
|
this.map.handleItemUse(player, item);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,20 +61,20 @@ export default class Inventory extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get activeTiles() {
|
private get activeTiles() {
|
||||||
return this.tiles.slice(0, this.characters.length)
|
return this.tiles.slice(0, this.players.length)
|
||||||
.flatMap((column, characterIndex) =>
|
.flatMap((column, playerIndex) =>
|
||||||
column.slice(0, this.characters[characterIndex].inventory.length)
|
column.slice(0, this.players[playerIndex].inventory.length)
|
||||||
.map((tile, tileIndex) => ({
|
.map((tile, tileIndex) => ({
|
||||||
tile,
|
tile,
|
||||||
character: this.characters[characterIndex],
|
player: this.players[playerIndex],
|
||||||
item: this.characters[characterIndex].inventory[tileIndex],
|
item: this.players[playerIndex].inventory[tileIndex],
|
||||||
}))
|
}))
|
||||||
.filter(({ item }) => item.isConsumable)
|
.filter(({ item }) => item.isConsumable)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override draw(ctx: CanvasRenderingContext2D): void {
|
protected override draw(ctx: CanvasRenderingContext2D): void {
|
||||||
const step = 1 / Object.keys(Characters).length;
|
const step = 1 / Object.keys(Players).length;
|
||||||
const columnWidth = this.width * step;
|
const columnWidth = this.width * step;
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
@ -82,8 +82,8 @@ export default class Inventory extends Entity {
|
||||||
ctx.font = `0.3px Arial`;
|
ctx.font = `0.3px Arial`;
|
||||||
ctx.fillStyle = 'black';
|
ctx.fillStyle = 'black';
|
||||||
|
|
||||||
for (const i of range(this.characters.length)) {
|
for (const i of range(this.players.length)) {
|
||||||
this.drawCharacter(ctx, i);
|
this.drawPlayer(ctx, i);
|
||||||
ctx.translate(1, 0);
|
ctx.translate(1, 0);
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
import Item, { ItemType, type ItemTypeImage } from "./item";
|
||||||
|
import Character from "./character";
|
||||||
|
import { SpinnerAction } from "./spinner";
|
||||||
|
import Tile from "./tile";
|
||||||
|
|
||||||
|
export default class Player extends Character {
|
||||||
|
public health: number;
|
||||||
|
public inventory: Item[] = [];
|
||||||
|
public lastDoor: Tile | undefined;
|
||||||
|
|
||||||
|
constructor(type: ItemTypeImage) {
|
||||||
|
super(type);
|
||||||
|
this.health = this.maxHealth;
|
||||||
|
const defaultItem = this.defaultItem;
|
||||||
|
if (defaultItem) {
|
||||||
|
this.inventory.push(this.defaultItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get maxHealth() {
|
||||||
|
return this.type === ItemType.CHAR_BIG ? 7 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultItem() {
|
||||||
|
if (this.type === ItemType.CHAR_NINJA) {
|
||||||
|
return new Item(ItemType.WEAPON_KNIFE);
|
||||||
|
}
|
||||||
|
if (this.type === ItemType.CHAR_POLICE) {
|
||||||
|
return new Item(ItemType.WEAPON_PISTOL);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get healingAmount() {
|
||||||
|
return this.type === ItemType.CHAR_NURSE ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get moveBonus() {
|
||||||
|
return this.type === ItemType.CHAR_RUNNER ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDead() {
|
||||||
|
return this.health <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get meleeWeapon() {
|
||||||
|
return this.inventory.find(i => i.isMeleeWeapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
get gun() {
|
||||||
|
return this.inventory.find(i => i.isShootingWeapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
get grenade() {
|
||||||
|
return this.inventory.find(i => i.type === ItemType.WEAPON_GRENADE);
|
||||||
|
}
|
||||||
|
|
||||||
|
get rocketLauncher() {
|
||||||
|
return this.inventory.find(i => i.type === ItemType.WEAPON_ROCKET_LAUNCHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public heal(player: Player) {
|
||||||
|
player.health += this.healingAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeItem(item: Item | null | undefined): boolean {
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const itemIndex = this.inventory.findIndex(i => i === item);
|
||||||
|
|
||||||
|
if (itemIndex >= 0) {
|
||||||
|
this.inventory.splice(itemIndex, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasItem(item: Item | null | undefined): boolean {
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const itemIndex = this.inventory.findIndex(i => i === item);
|
||||||
|
|
||||||
|
return itemIndex >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns true, if action was performed */
|
||||||
|
public handleSpin(action: SpinnerAction): boolean {
|
||||||
|
switch (action) {
|
||||||
|
case SpinnerAction.RUN:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SpinnerAction.BITE:
|
||||||
|
this.health -= 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SpinnerAction.MELEE:
|
||||||
|
return this.meleeWeapon != null && !this.tile.enemy?.isBoss;
|
||||||
|
|
||||||
|
case SpinnerAction.SHOOT:
|
||||||
|
return this.removeItem(this.gun) && !this.tile.enemy?.isBoss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Players = {
|
||||||
|
BIG: new Player(ItemType.CHAR_BIG),
|
||||||
|
NINJA: new Player(ItemType.CHAR_NINJA),
|
||||||
|
NURSE: new Player(ItemType.CHAR_NURSE),
|
||||||
|
POLICE: new Player(ItemType.CHAR_POLICE),
|
||||||
|
RUNNER: new Player(ItemType.CHAR_RUNNER),
|
||||||
|
} as const;
|
||||||
|
|
@ -3,7 +3,7 @@ import Entity from "./entity";
|
||||||
import Tile, { TileType } from "./tile";
|
import Tile, { TileType } from "./tile";
|
||||||
import Pathfinding from "./pathfinding";
|
import Pathfinding from "./pathfinding";
|
||||||
import Item, { ItemType } from "./item";
|
import Item, { ItemType } from "./item";
|
||||||
import Character, { Characters } from "./character";
|
import Player, { Players } from "./player";
|
||||||
import { SpinnerAction } from "./spinner";
|
import { SpinnerAction } from "./spinner";
|
||||||
|
|
||||||
enum GameState {
|
enum GameState {
|
||||||
|
|
@ -15,14 +15,14 @@ export default class TileMap extends Entity {
|
||||||
private tiles: Tile[] = [];
|
private tiles: Tile[] = [];
|
||||||
public startTile: Tile;
|
public startTile: Tile;
|
||||||
|
|
||||||
public readonly characters: Character[];
|
public readonly players: Player[];
|
||||||
private currentCharacterIdx = 0;
|
private currentPlayerIdx = 0;
|
||||||
private state = GameState.NORMAL;
|
private state = GameState.NORMAL;
|
||||||
private availableTiles: Tile[] = [];
|
private availableTiles: Tile[] = [];
|
||||||
|
|
||||||
constructor(position: [number, number], private mapSize: number, private tileSize: number, numPlayers: number = 2) {
|
constructor(position: [number, number], private mapSize: number, private tileSize: number, numPlayers: number = 2) {
|
||||||
super(position, [mapSize * tileSize, mapSize * tileSize]);
|
super(position, [mapSize * tileSize, mapSize * tileSize]);
|
||||||
this.characters = shuffle(Object.values(Characters)).slice(0, numPlayers);
|
this.players = shuffle(Object.values(Players)).slice(0, numPlayers);
|
||||||
this.startTile = this.createMap();
|
this.startTile = this.createMap();
|
||||||
this.findAvailableTiles();
|
this.findAvailableTiles();
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ export default class TileMap extends Entity {
|
||||||
this.startTile = startTile;
|
this.startTile = startTile;
|
||||||
|
|
||||||
this.fillItems();
|
this.fillItems();
|
||||||
this.characters.forEach(c => c.tile = startTile);
|
this.players.forEach(c => c.tile = startTile);
|
||||||
|
|
||||||
return startTile;
|
return startTile;
|
||||||
}
|
}
|
||||||
|
|
@ -164,8 +164,8 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const char of Object.values(Characters)) {
|
for (const char of Object.values(Players)) {
|
||||||
if (this.characters.includes(char)) continue;
|
if (this.players.includes(char)) continue;
|
||||||
items.push(char);
|
items.push(char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,19 +187,19 @@ export default class TileMap extends Entity {
|
||||||
|
|
||||||
for (const [tile, item] of zip(fillableTiles, shuffle(items))) {
|
for (const [tile, item] of zip(fillableTiles, shuffle(items))) {
|
||||||
tile.items.push(item);
|
tile.items.push(item);
|
||||||
if (item instanceof Character) {
|
if (item instanceof Player) {
|
||||||
item.tile = tile;
|
item.tile = tile;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get character() {
|
get player() {
|
||||||
return this.characters[this.currentCharacterIdx];
|
return this.players[this.currentPlayerIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeTiles() {
|
get activeTiles() {
|
||||||
return this.tiles.filter(tile => (
|
return this.tiles.filter(tile => (
|
||||||
tile.items.length > 0
|
tile.items.length > 0
|
||||||
|| tile.type === TileType.LOCKED_DOOR
|
|| tile.type === TileType.LOCKED_DOOR
|
||||||
|| tile.hovered
|
|| tile.hovered
|
||||||
|| (this.state === GameState.NORMAL && this.availableTiles.includes(tile))
|
|| (this.state === GameState.NORMAL && this.availableTiles.includes(tile))
|
||||||
|
|
@ -216,21 +216,21 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
for (const tile of this.availableTiles) {
|
for (const tile of this.availableTiles) {
|
||||||
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
||||||
const path = Pathfinding.findPath(this.character.tile, tile);
|
const path = Pathfinding.findPath(this.player.tile, tile);
|
||||||
if (path.length > 1) {
|
if (path.length > 1) {
|
||||||
this.character.lastDoor = path.find(t => t.type === TileType.DOOR);
|
this.player.lastDoor = path.find(t => t.type === TileType.DOOR);
|
||||||
this.character.tile = tile;
|
this.player.moveTo(tile, path);
|
||||||
tile.open();
|
tile.open();
|
||||||
for (const item of tile.items.slice()) { // iterate remaining items
|
for (const item of tile.items.slice()) { // iterate remaining items
|
||||||
if (item.isPickable) {
|
if (item.isPickable) {
|
||||||
if (!tile.enemy) {
|
if (!tile.enemy) {
|
||||||
tile.removeItem(item);
|
tile.removeItem(item);
|
||||||
this.character.inventory.push(item);
|
this.player.inventory.push(item);
|
||||||
}
|
}
|
||||||
} else if (item instanceof Character) {
|
} else if (item instanceof Player) {
|
||||||
tile.removeItem(item);
|
tile.removeItem(item);
|
||||||
this.characters.push(item);
|
this.players.push(item);
|
||||||
} else if (item.isBoss && this.character.removeItem(this.character.rocketLauncher)) {
|
} else if (item.isBoss && this.player.removeItem(this.player.rocketLauncher)) {
|
||||||
tile.killEnemy();
|
tile.killEnemy();
|
||||||
} else if (item.isEnemy) {
|
} else if (item.isEnemy) {
|
||||||
this.state = GameState.FIGHT;
|
this.state = GameState.FIGHT;
|
||||||
|
|
@ -240,7 +240,7 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.state === GameState.NORMAL) {
|
if (this.state === GameState.NORMAL) {
|
||||||
this.nextCharacter();
|
this.nextPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,14 +256,14 @@ export default class TileMap extends Entity {
|
||||||
this.findAvailableTiles(action);
|
this.findAvailableTiles(action);
|
||||||
break;
|
break;
|
||||||
case GameState.FIGHT:
|
case GameState.FIGHT:
|
||||||
const success = this.character.handleSpin(action);
|
const success = this.player.handleSpin(action);
|
||||||
if (success) {
|
if (success) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case SpinnerAction.BITE:
|
case SpinnerAction.BITE:
|
||||||
if (this.character.isDead) {
|
if (this.player.isDead) {
|
||||||
this.character.tile.items.push(...this.character.inventory);
|
this.player.tile.items.push(...this.player.inventory);
|
||||||
this.characters.splice(this.currentCharacterIdx, 1);
|
this.players.splice(this.currentPlayerIdx, 1);
|
||||||
this.currentCharacterIdx = this.currentCharacterIdx % this.characters.length;
|
this.currentPlayerIdx = this.currentPlayerIdx % this.players.length;
|
||||||
this.setNormalState();
|
this.setNormalState();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -280,29 +280,29 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleItemUse(character: Character, item: Item) {
|
public handleItemUse(player: Player, item: Item) {
|
||||||
let success = character.hasItem(item);
|
let success = player.hasItem(item);
|
||||||
if (success) {
|
if (success) {
|
||||||
if (item.type === ItemType.ITEM_HEAL) {
|
if (item.type === ItemType.ITEM_HEAL) {
|
||||||
character.heal(character);
|
player.heal(player);
|
||||||
} else if (item.type === ItemType.WEAPON_GRENADE && this.state === GameState.FIGHT) {
|
} else if (item.type === ItemType.WEAPON_GRENADE && this.state === GameState.FIGHT) {
|
||||||
this.killEnemy();
|
this.killEnemy();
|
||||||
} else if (item.type === ItemType.ITEM_PLANKS && character.lastDoor && !character.lastDoor.enemy) {
|
} else if (item.type === ItemType.ITEM_PLANKS && player.lastDoor && !player.lastDoor.enemy) {
|
||||||
character.lastDoor.type = TileType.LOCKED_DOOR;
|
player.lastDoor.type = TileType.LOCKED_DOOR;
|
||||||
} else {
|
} else {
|
||||||
success = false;
|
success = false;
|
||||||
console.warn(`Dont know how to use ${item}`);
|
console.warn(`Dont know how to use ${item}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
character.removeItem(item);
|
player.removeItem(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private findAvailableTiles(moveDistance: number = 1) {
|
private findAvailableTiles(moveDistance: number = 1) {
|
||||||
const characterTiles = new Set(this.characters.map(c => c.tile));
|
const playerTiles = new Set(this.players.map(c => c.tile));
|
||||||
this.availableTiles = Pathfinding.findPossibleMoves(this.character.tile, moveDistance + this.character.moveBonus)
|
this.availableTiles = Pathfinding.findPossibleMoves(this.player.tile, moveDistance + this.player.moveBonus)
|
||||||
.filter(t => !characterTiles.has(t));
|
.filter(t => !playerTiles.has(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
private setNormalState() {
|
private setNormalState() {
|
||||||
|
|
@ -310,19 +310,19 @@ export default class TileMap extends Entity {
|
||||||
this.findAvailableTiles();
|
this.findAvailableTiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
private nextCharacter() {
|
private nextPlayer() {
|
||||||
this.currentCharacterIdx = (this.currentCharacterIdx + 1) % this.characters.length;
|
this.currentPlayerIdx = (this.currentPlayerIdx + 1) % this.players.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private killEnemy() {
|
private killEnemy() {
|
||||||
this.character.tile.killEnemy();
|
this.player.tile.killEnemy();
|
||||||
this.character.tile.items.forEach((item) => {
|
this.player.tile.items.slice().forEach((item) => {
|
||||||
if (item.isPickable) {
|
if (item.isPickable) {
|
||||||
this.character.inventory.push(item);
|
this.player.inventory.push(item);
|
||||||
this.character.tile.removeItem(item);
|
this.player.tile.removeItem(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.nextCharacter();
|
this.nextPlayer();
|
||||||
this.setNormalState();
|
this.setNormalState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,15 +339,20 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = this.tileSize * 0.8;
|
const w = this.tileSize * 0.8;
|
||||||
[...this.characters, this.character].forEach(c =>
|
[...this.players, this.player].forEach((player) => {
|
||||||
ctx.drawImage(c.type, c.tile.centerX - w / 2, c.tile.centerY - w / 2, w, w)
|
const [x, y] = player.displayPosition;
|
||||||
);
|
ctx.drawImage(player.type, x - w / 2, y - w / 2, w, w);
|
||||||
|
});
|
||||||
|
|
||||||
this.activeTiles.forEach(t => t.render(ctx));
|
this.activeTiles.forEach(t => t.render(ctx));
|
||||||
|
|
||||||
ctx.lineWidth = 5;
|
ctx.lineWidth = 5;
|
||||||
ctx.strokeStyle = 'yellow';
|
ctx.strokeStyle = 'yellow';
|
||||||
|
|
||||||
ctx.strokeRect(this.character.tile.centerX - w / 2, this.character.tile.centerY - w / 2, w, w);
|
ctx.strokeRect(this.player.tile.centerX - w / 2, this.player.tile.centerY - w / 2, w, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(dt: number) {
|
||||||
|
this.players.forEach((player) => player.update(dt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue