Items transfer & healing
This commit is contained in:
parent
c66446c2b3
commit
71ea755032
|
|
@ -93,6 +93,11 @@ export async function buildHTML(game: string, { production = false, portable = f
|
||||||
if (production) {
|
if (production) {
|
||||||
const minifyResult = UglifyJS.minify(script, {
|
const minifyResult = UglifyJS.minify(script, {
|
||||||
module: true,
|
module: true,
|
||||||
|
toplevel: true,
|
||||||
|
mangle: {
|
||||||
|
toplevel: true,
|
||||||
|
properties: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (minifyResult.error) {
|
if (minifyResult.error) {
|
||||||
console.warn(`Minify error: ${minifyResult.error}`);
|
console.warn(`Minify error: ${minifyResult.error}`);
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,24 @@ 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";
|
||||||
|
import { ItemType } from "./item";
|
||||||
|
|
||||||
export type UseListener = (player: Player, item: Item) => void;
|
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 {
|
export default class Inventory extends Entity {
|
||||||
|
private playerTiles: Tile[];
|
||||||
|
private transferButtons: Tile[];
|
||||||
|
private healButtons: Tile[];
|
||||||
private tiles: Tile[][];
|
private tiles: Tile[][];
|
||||||
|
private transferTarget: Player | undefined;
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -18,6 +31,18 @@ export default class Inventory extends Entity {
|
||||||
const tileSize = this.width / numPlayers;
|
const tileSize = this.width / numPlayers;
|
||||||
const numTiles = Math.floor(this.height / tileSize) - 2;
|
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) =>
|
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)
|
||||||
|
|
@ -29,17 +54,118 @@ export default class Inventory extends Entity {
|
||||||
return this.map.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.map.canTransferTo(this.player, player)),
|
||||||
|
|
||||||
|
...this.healButtons
|
||||||
|
.slice(0, this.players.length)
|
||||||
|
.map((tile, playerIndex) => ({
|
||||||
|
tile,
|
||||||
|
player: this.players[playerIndex],
|
||||||
|
heal: true,
|
||||||
|
}))
|
||||||
|
.filter(({ player }) =>
|
||||||
|
this.player.hasItem(ItemType.ITEM_HEALING_KIT)
|
||||||
|
&& this.map.canHeal(this.player, player)
|
||||||
|
),
|
||||||
|
|
||||||
|
...this.tiles
|
||||||
|
.slice(0, this.players.length)
|
||||||
|
.flatMap((column, playerIndex) =>
|
||||||
|
column
|
||||||
|
.slice(0, this.players[playerIndex].inventory.length)
|
||||||
|
.map((tile, tileIndex) => ({
|
||||||
|
tile,
|
||||||
|
player: this.players[playerIndex],
|
||||||
|
item: this.players[playerIndex].inventory[tileIndex],
|
||||||
|
}))
|
||||||
|
.filter(({ item, player }) => this.transferTarget
|
||||||
|
? this.player === player
|
||||||
|
: 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.map.handleHeal(player);
|
||||||
|
} else if (transfer) {
|
||||||
|
this.handleToggleTransfer(player);
|
||||||
|
} else if (this.transferTarget && item) {
|
||||||
|
this.handleTransfer(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(item: Item) {
|
||||||
|
if (this.transferTarget) {
|
||||||
|
const itemExisted = this.player.removeItem(item);
|
||||||
|
if (itemExisted) {
|
||||||
|
this.transferTarget.inventory.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.transferTarget = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private drawPlayer(ctx: CanvasRenderingContext2D, idx: number) {
|
private drawPlayer(ctx: CanvasRenderingContext2D, idx: number) {
|
||||||
const player = this.players[idx];
|
const player = this.players[idx];
|
||||||
|
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
ctx.drawImage(player.type, 0.1, 0.1, 0.8, 0.8);
|
ctx.drawImage(player.type, 0.1, 0.1, 0.8, 0.8);
|
||||||
ctx.fillText(`💖 ${player.health}`, 0.5, 1.5);
|
ctx.fillText(`💖 ${player.health}`, 0.5, 1.2);
|
||||||
if (player === this.map.player) {
|
|
||||||
|
if (player === this.player) {
|
||||||
ctx.strokeStyle = 'black';
|
ctx.strokeStyle = 'black';
|
||||||
ctx.lineWidth = 0.03;
|
ctx.lineWidth = 0.03;
|
||||||
|
|
||||||
ctx.strokeRect(0.1, 0.1, 0.8, 0.8);
|
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;
|
let y = 2;
|
||||||
for (const item of player.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);
|
||||||
|
|
@ -47,32 +173,6 @@ export default class Inventory extends Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override handleClick(x: number, y: number): void {
|
|
||||||
for (const { tile, item, player } of this.activeTiles) {
|
|
||||||
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
|
||||||
this.map.handleItemUse(player, item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override handleMouseMove(x: number, y: number): void {
|
|
||||||
this.activeTiles.forEach(({ tile }) => tile.handleMouseMove(x - this.left, y - this.top));
|
|
||||||
}
|
|
||||||
|
|
||||||
private get activeTiles() {
|
|
||||||
return this.tiles.slice(0, this.players.length)
|
|
||||||
.flatMap((column, playerIndex) =>
|
|
||||||
column.slice(0, this.players[playerIndex].inventory.length)
|
|
||||||
.map((tile, tileIndex) => ({
|
|
||||||
tile,
|
|
||||||
player: this.players[playerIndex],
|
|
||||||
item: this.players[playerIndex].inventory[tileIndex],
|
|
||||||
}))
|
|
||||||
.filter(({ item }) => item.isConsumable)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override draw(ctx: CanvasRenderingContext2D): void {
|
protected override draw(ctx: CanvasRenderingContext2D): void {
|
||||||
const step = 1 / Object.keys(Players).length;
|
const step = 1 / Object.keys(Players).length;
|
||||||
const columnWidth = this.width * step;
|
const columnWidth = this.width * step;
|
||||||
|
|
@ -89,6 +189,28 @@ export default class Inventory extends Entity {
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
ctx.scale(1 / this.width, 1 / this.height);
|
ctx.scale(1 / this.width, 1 / this.height);
|
||||||
|
|
||||||
|
ctx.font = `${0.3 * columnWidth}px Arial`;
|
||||||
|
this.transferButtons.slice(0, this.players.length)
|
||||||
|
.map((tile, playerIdx) => ({ tile, player: this.players[playerIdx] }))
|
||||||
|
.filter(({ player }) => this.map.canTransferTo(this.player, player))
|
||||||
|
.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)
|
||||||
|
.map((tile, playerIdx) => ({ tile, player: this.players[playerIdx] }))
|
||||||
|
.filter(({ player }) => this.map.canHeal(this.player, player))
|
||||||
|
.forEach(({ tile }) => {
|
||||||
|
ctx.fillText('🩹', tile.centerX, tile.centerY);
|
||||||
|
});
|
||||||
|
|
||||||
this.activeTiles.forEach(({ tile }) => tile.render(ctx));
|
this.activeTiles.forEach(({ tile }) => tile.render(ctx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ export const ItemType = {
|
||||||
ENEMY_ZOMBIE: zombie,
|
ENEMY_ZOMBIE: zombie,
|
||||||
|
|
||||||
ITEM_FUEL: fuel,
|
ITEM_FUEL: fuel,
|
||||||
ITEM_HEAL: heal,
|
ITEM_HEALING_KIT: heal,
|
||||||
ITEM_KEYS: keys,
|
ITEM_KEYS: keys,
|
||||||
ITEM_PLANKS: planks,
|
ITEM_PLANKS: planks,
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ export default class Item {
|
||||||
get isItem() {
|
get isItem() {
|
||||||
return [
|
return [
|
||||||
ItemType.ITEM_FUEL,
|
ItemType.ITEM_FUEL,
|
||||||
ItemType.ITEM_HEAL,
|
ItemType.ITEM_HEALING_KIT,
|
||||||
ItemType.ITEM_KEYS,
|
ItemType.ITEM_KEYS,
|
||||||
ItemType.ITEM_PLANKS,
|
ItemType.ITEM_PLANKS,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
|
|
@ -109,7 +109,6 @@ export default class Item {
|
||||||
|
|
||||||
get isConsumable() {
|
get isConsumable() {
|
||||||
return [
|
return [
|
||||||
ItemType.ITEM_HEAL,
|
|
||||||
ItemType.ITEM_PLANKS,
|
ItemType.ITEM_PLANKS,
|
||||||
ItemType.WEAPON_GRENADE,
|
ItemType.WEAPON_GRENADE,
|
||||||
].includes(this.type);
|
].includes(this.type);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export default class Player extends Character {
|
||||||
public health: number;
|
public health: number;
|
||||||
public inventory: Item[] = [];
|
public inventory: Item[] = [];
|
||||||
public lastDoor: Tile | undefined;
|
public lastDoor: Tile | undefined;
|
||||||
|
public active = true;
|
||||||
|
|
||||||
constructor(type: ItemTypeImage) {
|
constructor(type: ItemTypeImage) {
|
||||||
super(type);
|
super(type);
|
||||||
|
|
@ -63,6 +64,10 @@ export default class Player extends Character {
|
||||||
return this.inventory.find(i => i.type === ItemType.ITEM_FUEL);
|
return this.inventory.find(i => i.type === ItemType.ITEM_FUEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get healingKit() {
|
||||||
|
return this.inventory.find(i => i.type === ItemType.ITEM_HEALING_KIT);
|
||||||
|
}
|
||||||
|
|
||||||
public heal(player: Player) {
|
public heal(player: Player) {
|
||||||
player.health += this.healingAmount;
|
player.health += this.healingAmount;
|
||||||
}
|
}
|
||||||
|
|
@ -90,6 +95,10 @@ export default class Player extends Character {
|
||||||
return itemIndex >= 0;
|
return itemIndex >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleActive() {
|
||||||
|
this.active = !this.active;
|
||||||
|
}
|
||||||
|
|
||||||
/** @returns true, if action was performed */
|
/** @returns true, if action was performed */
|
||||||
public handleSpin(action: SpinnerAction): boolean {
|
public handleSpin(action: SpinnerAction): boolean {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ export default class TileMap extends Entity {
|
||||||
[ItemType.ENEMY_ZOMBIE, 17],
|
[ItemType.ENEMY_ZOMBIE, 17],
|
||||||
|
|
||||||
[ItemType.ITEM_FUEL, 1],
|
[ItemType.ITEM_FUEL, 1],
|
||||||
[ItemType.ITEM_HEAL, 6],
|
[ItemType.ITEM_HEALING_KIT, 6],
|
||||||
[ItemType.ITEM_KEYS, 1],
|
[ItemType.ITEM_KEYS, 1],
|
||||||
[ItemType.ITEM_PLANKS, 8],
|
[ItemType.ITEM_PLANKS, 8],
|
||||||
|
|
||||||
|
|
@ -241,6 +241,38 @@ export default class TileMap extends Entity {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public canTransferTo(player: Player, otherPlayer: Player): boolean {
|
||||||
|
if (otherPlayer === player) return false;
|
||||||
|
|
||||||
|
if (player.inventory.length === 0) return false;
|
||||||
|
|
||||||
|
if (player.tile === otherPlayer.tile) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const tile of player.tile.connections) {
|
||||||
|
if (otherPlayer.tile === tile) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canHeal(player: Player, otherPlayer: Player): boolean {
|
||||||
|
if (!player.healingKit) return false;
|
||||||
|
|
||||||
|
return player === otherPlayer || this.canTransferTo(player, otherPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleHeal(target: Player) {
|
||||||
|
if (target) {
|
||||||
|
const itemExisted = this.player.removeItem(this.player.healingKit);
|
||||||
|
if (itemExisted) {
|
||||||
|
this.player.heal(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override handleMouseMove(x: number, y: number): void {
|
public override handleMouseMove(x: number, y: number): void {
|
||||||
this.activeTiles.forEach(tile => tile.handleMouseMove(x - this.left, y - this.top));
|
this.activeTiles.forEach(tile => tile.handleMouseMove(x - this.left, y - this.top));
|
||||||
}
|
}
|
||||||
|
|
@ -334,9 +366,7 @@ export default class TileMap extends Entity {
|
||||||
public handleItemUse(player: Player, item: Item) {
|
public handleItemUse(player: Player, item: Item) {
|
||||||
let success = player.hasItem(item);
|
let success = player.hasItem(item);
|
||||||
if (success) {
|
if (success) {
|
||||||
if (item.type === ItemType.ITEM_HEAL) {
|
if (item.type === ItemType.WEAPON_GRENADE && this.state === GameState.FIGHT) {
|
||||||
player.heal(player);
|
|
||||||
} else if (item.type === ItemType.WEAPON_GRENADE && this.state === GameState.FIGHT) {
|
|
||||||
this.killEnemy();
|
this.killEnemy();
|
||||||
} else if (item.type === ItemType.ITEM_PLANKS && player.lastDoor && !player.lastDoor.enemy) {
|
} else if (item.type === ItemType.ITEM_PLANKS && player.lastDoor && !player.lastDoor.enemy) {
|
||||||
player.lastDoor.type = TileType.LOCKED_DOOR;
|
player.lastDoor.type = TileType.LOCKED_DOOR;
|
||||||
|
|
@ -363,7 +393,23 @@ export default class TileMap extends Entity {
|
||||||
|
|
||||||
private nextPlayer() {
|
private nextPlayer() {
|
||||||
this.spinner.stop();
|
this.spinner.stop();
|
||||||
this.currentPlayerIdx = (this.currentPlayerIdx + 1) % this.players.length;
|
let overflow = false;
|
||||||
|
let startIndex = this.currentPlayerIdx;
|
||||||
|
do {
|
||||||
|
let nextIdx = this.currentPlayerIdx + 1;
|
||||||
|
if (nextIdx >= this.players.length) {
|
||||||
|
if (overflow) {
|
||||||
|
this.currentPlayerIdx = 0;
|
||||||
|
this.player.active = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
overflow = true;
|
||||||
|
nextIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentPlayerIdx = nextIdx;
|
||||||
|
} while (!this.player.active);
|
||||||
|
|
||||||
if (this.win) {
|
if (this.win) {
|
||||||
const endTile = this.tiles.findLast(t => t.type === TileType.END);
|
const endTile = this.tiles.findLast(t => t.type === TileType.END);
|
||||||
if (endTile) {
|
if (endTile) {
|
||||||
|
|
@ -373,7 +419,7 @@ export default class TileMap extends Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(alert, 2000, "🎉🎉🎉 ПОБЕДА! 🚗 🎉🎉🎉");
|
setTimeout(alert, 2000, "🎉🎉🎉 ПОБЕДА! 🚗 🎉🎉🎉");
|
||||||
} else if (this.currentPlayerIdx === 0 && this.players.length < this.foundPlayers.size) { // if someone is dead
|
} else if (overflow && this.players.length < this.foundPlayers.size) { // if someone is dead
|
||||||
const enemies = this.enemies;
|
const enemies = this.enemies;
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
|
|
@ -388,7 +434,7 @@ export default class TileMap extends Entity {
|
||||||
const enemy = enemyTile.enemy!;
|
const enemy = enemyTile.enemy!;
|
||||||
const allowedSteps = randInt(1, 5) + enemy.moveBonus;
|
const allowedSteps = randInt(1, 5) + enemy.moveBonus;
|
||||||
|
|
||||||
console.log({enemy, allowedSteps});
|
console.log({ enemy, allowedSteps });
|
||||||
if (allowedSteps <= 0) continue;
|
if (allowedSteps <= 0) continue;
|
||||||
|
|
||||||
const path = Pathfinding.findPath(enemyTile, player.tile, true);
|
const path = Pathfinding.findPath(enemyTile, player.tile, true);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue