236 lines
7.7 KiB
TypeScript
236 lines
7.7 KiB
TypeScript
import { range } from "@common/utils";
|
|
import type Player from "./player";
|
|
import { Players } from "./player";
|
|
import Entity from "./entity";
|
|
import Tile from "./tile";
|
|
import type Item from "./item";
|
|
import type TileMap from "./tilemap";
|
|
import { ItemType } from "./item";
|
|
|
|
export type UseListener = (player: Player, item: Item) => void;
|
|
|
|
interface ActiveTile {
|
|
tile: Tile;
|
|
player: Player;
|
|
transfer?: boolean;
|
|
heal?: boolean;
|
|
item?: Item;
|
|
}
|
|
|
|
export default class Inventory extends Entity {
|
|
private playerTiles: Tile[];
|
|
private transferButtons: Tile[];
|
|
private healButtons: Tile[];
|
|
private tiles: Tile[][];
|
|
private transferTarget: Player | undefined;
|
|
|
|
constructor(public readonly map: TileMap, position: [number, number], size: [number, number]) {
|
|
super(position, size);
|
|
|
|
const numPlayers = Object.keys(Players).length;
|
|
const tileSize = this.width / numPlayers;
|
|
const numTiles = Math.floor(this.height / tileSize) - 2;
|
|
|
|
this.playerTiles = range(numPlayers).map((x) =>
|
|
new Tile([x * tileSize, 0], tileSize)
|
|
);
|
|
|
|
this.transferButtons = range(numPlayers).map((x) =>
|
|
new Tile([x * tileSize, 1.5 * tileSize], tileSize / 2)
|
|
);
|
|
|
|
this.healButtons = range(numPlayers).map((x) =>
|
|
new Tile([(x + 0.5) * tileSize, 1.5 * tileSize], tileSize / 2)
|
|
);
|
|
|
|
this.tiles = range(numPlayers).map((x) =>
|
|
range(numTiles).map((y) =>
|
|
new Tile([x * tileSize, (2 + y) * tileSize], tileSize)
|
|
)
|
|
);
|
|
}
|
|
|
|
private get players() {
|
|
return this.map.players;
|
|
}
|
|
|
|
private get player() {
|
|
return this.map.player;
|
|
}
|
|
|
|
private get activeTiles(): ActiveTile[] {
|
|
return [
|
|
...this.playerTiles
|
|
.slice(0, this.players.length)
|
|
.map((tile, playerIndex) => ({
|
|
tile,
|
|
player: this.players[playerIndex],
|
|
})),
|
|
|
|
...this.transferButtons
|
|
.slice(0, this.players.length)
|
|
.map((tile, playerIndex) => ({
|
|
tile,
|
|
player: this.players[playerIndex],
|
|
transfer: true,
|
|
}))
|
|
.filter(({ player }) =>
|
|
this.players.some(p => this.map.canTransferTo(p, player))
|
|
),
|
|
|
|
...this.healButtons
|
|
.slice(0, this.players.length)
|
|
.map((tile, playerIndex) => ({
|
|
tile,
|
|
player: this.players[playerIndex],
|
|
heal: true,
|
|
}))
|
|
.filter(({ player }) =>
|
|
this.players.some(healer => this.map.canHeal(healer, player))
|
|
),
|
|
|
|
...this.tiles
|
|
.slice(0, this.players.length)
|
|
.flatMap((column, playerIndex) =>
|
|
column
|
|
.slice(0, this.players[playerIndex].numItems)
|
|
.map((tile, tileIndex) => ({
|
|
tile,
|
|
player: this.players[playerIndex],
|
|
item: this.players[playerIndex].getItem(tileIndex),
|
|
}))
|
|
.filter(({ item, player }) => this.transferTarget
|
|
? this.map.canTransferTo(player, this.transferTarget)
|
|
: item.isConsumable)
|
|
)
|
|
];
|
|
}
|
|
|
|
public override handleClick(x: number, y: number): void {
|
|
for (const { tile, item, player, transfer, heal } of this.activeTiles) {
|
|
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
|
if (heal) {
|
|
this.handleHeal(player);
|
|
} else if (transfer) {
|
|
this.handleToggleTransfer(player);
|
|
} else if (this.transferTarget && item) {
|
|
this.handleTransfer(player, item);
|
|
} else if (item) {
|
|
this.map.handleItemUse(player, item);
|
|
} else if (player !== this.player) {
|
|
player.toggleActive();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override handleMouseMove(x: number, y: number): void {
|
|
this.activeTiles.forEach(({ tile }) => tile.handleMouseMove(x - this.left, y - this.top));
|
|
this.playerTiles.forEach((tile) => tile.handleMouseMove(x - this.left, y - this.top));
|
|
}
|
|
|
|
private handleToggleTransfer(target: Player) {
|
|
if (this.transferTarget === target) {
|
|
this.transferTarget = undefined;
|
|
} else {
|
|
this.transferTarget = target;
|
|
}
|
|
}
|
|
|
|
private handleTransfer(player: Player, item: Item) {
|
|
if (this.transferTarget) {
|
|
const itemExisted = player.removeItem(item);
|
|
if (itemExisted) {
|
|
this.transferTarget.addItem(item);
|
|
}
|
|
}
|
|
this.transferTarget = undefined;
|
|
}
|
|
|
|
private handleHeal(target: Player) {
|
|
const healers = [
|
|
this.players.find(p => p.type === ItemType.CHAR_NURSE),
|
|
target,
|
|
...this.players,
|
|
];
|
|
for (const healer of healers) {
|
|
if (!healer || !this.map.canHeal(healer, target)) continue;
|
|
|
|
const itemExisted = healer.removeItem(healer.healingKit);
|
|
if (itemExisted) {
|
|
healer.heal(target);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private drawPlayer(ctx: CanvasRenderingContext2D, idx: number) {
|
|
const player = this.players[idx];
|
|
|
|
ctx.fillStyle = 'black';
|
|
ctx.drawImage(player.type, 0.1, 0.1, 0.8, 0.8);
|
|
ctx.fillText(`💖 ${player.health}`, 0.5, 1.2);
|
|
|
|
if (player === this.player) {
|
|
ctx.strokeStyle = 'black';
|
|
ctx.lineWidth = 0.03;
|
|
|
|
ctx.strokeRect(0.1, 0.1, 0.8, 0.8);
|
|
}
|
|
|
|
if (!player.active) {
|
|
ctx.fillStyle = `rgba(255, 255, 255, 0.5)`;
|
|
ctx.fillRect(0.1, 0.1, 0.8, 0.8);
|
|
}
|
|
|
|
let y = 2;
|
|
for (const item of player) {
|
|
ctx.drawImage(item.type, 0.1, 0.1 + y, 0.8, 0.8);
|
|
y += 1;
|
|
}
|
|
}
|
|
|
|
protected override draw(ctx: CanvasRenderingContext2D): void {
|
|
const step = 1 / Object.keys(Players).length;
|
|
const columnWidth = this.width * step;
|
|
|
|
ctx.save();
|
|
ctx.scale(step, columnWidth / this.height);
|
|
ctx.font = `0.3px Arial`;
|
|
ctx.fillStyle = 'black';
|
|
|
|
for (const i of range(this.players.length)) {
|
|
this.drawPlayer(ctx, i);
|
|
ctx.translate(1, 0);
|
|
}
|
|
ctx.restore();
|
|
|
|
ctx.scale(1 / this.width, 1 / this.height);
|
|
|
|
ctx.font = `${0.3 * columnWidth}px Arial`;
|
|
|
|
const activeTiles = new Set(this.activeTiles.map(({ tile }) => tile));
|
|
|
|
this.transferButtons.slice(0, this.players.length)
|
|
.map((tile, playerIdx) => ({ tile, player: this.players[playerIdx] }))
|
|
.filter(({ tile }) => activeTiles.has(tile))
|
|
.forEach(({ tile, player }) => {
|
|
if (player === this.transferTarget) {
|
|
ctx.fillStyle = 'lime';
|
|
ctx.fillRect(tile.left, tile.top, tile.width, tile.height);
|
|
}
|
|
ctx.fillStyle = 'black';
|
|
ctx.fillText('🎁', tile.centerX, tile.centerY);
|
|
});
|
|
|
|
ctx.fillStyle = 'black';
|
|
this.healButtons.slice(0, this.players.length)
|
|
.filter((tile) => activeTiles.has(tile))
|
|
.forEach((tile) => {
|
|
ctx.fillText('🩹', tile.centerX, tile.centerY);
|
|
});
|
|
|
|
activeTiles.forEach((tile) => tile.render(ctx));
|
|
}
|
|
} |