1
0
Fork 0

Fix transfer

This commit is contained in:
Pabloader 2025-07-01 11:47:38 +00:00
parent 71ea755032
commit bad3d62449
5 changed files with 113 additions and 56 deletions

View File

@ -11,7 +11,7 @@ const MAP_SIZE = 12;
const MAP_PIXEL_SIZE = window.innerHeight; const MAP_PIXEL_SIZE = window.innerHeight;
const MAP_PADDING = 10; const MAP_PADDING = 10;
const TILE_SIZE = (MAP_PIXEL_SIZE - MAP_PADDING * 2) / MAP_SIZE; const TILE_SIZE = (MAP_PIXEL_SIZE - MAP_PADDING * 2) / MAP_SIZE;
const SIDEBAR_SIZE = clamp((window.innerWidth - window.innerHeight) / 2, 300, 600); const SIDEBAR_SIZE = clamp((window.innerWidth - window.innerHeight) / 2, MAP_PADDING + 5 * TILE_SIZE, window.innerHeight / 2);
const canvas = createCanvas(MAP_PIXEL_SIZE + SIDEBAR_SIZE * 2, MAP_PIXEL_SIZE); const canvas = createCanvas(MAP_PIXEL_SIZE + SIDEBAR_SIZE * 2, MAP_PIXEL_SIZE);

View File

@ -74,7 +74,9 @@ export default class Inventory extends Entity {
player: this.players[playerIndex], player: this.players[playerIndex],
transfer: true, transfer: true,
})) }))
.filter(({ player }) => this.map.canTransferTo(this.player, player)), .filter(({ player }) =>
this.players.some(p => this.map.canTransferTo(p, player))
),
...this.healButtons ...this.healButtons
.slice(0, this.players.length) .slice(0, this.players.length)
@ -84,22 +86,21 @@ export default class Inventory extends Entity {
heal: true, heal: true,
})) }))
.filter(({ player }) => .filter(({ player }) =>
this.player.hasItem(ItemType.ITEM_HEALING_KIT) this.players.some(healer => this.map.canHeal(healer, player))
&& this.map.canHeal(this.player, player)
), ),
...this.tiles ...this.tiles
.slice(0, this.players.length) .slice(0, this.players.length)
.flatMap((column, playerIndex) => .flatMap((column, playerIndex) =>
column column
.slice(0, this.players[playerIndex].inventory.length) .slice(0, this.players[playerIndex].numItems)
.map((tile, tileIndex) => ({ .map((tile, tileIndex) => ({
tile, tile,
player: this.players[playerIndex], player: this.players[playerIndex],
item: this.players[playerIndex].inventory[tileIndex], item: this.players[playerIndex].getItem(tileIndex),
})) }))
.filter(({ item, player }) => this.transferTarget .filter(({ item, player }) => this.transferTarget
? this.player === player ? this.map.canTransferTo(player, this.transferTarget)
: item.isConsumable) : item.isConsumable)
) )
]; ];
@ -109,11 +110,11 @@ export default class Inventory extends Entity {
for (const { tile, item, player, transfer, heal } of this.activeTiles) { for (const { tile, item, player, transfer, heal } of this.activeTiles) {
if (tile.isPointInBounds(x - this.left, y - this.top)) { if (tile.isPointInBounds(x - this.left, y - this.top)) {
if (heal) { if (heal) {
this.map.handleHeal(player); this.handleHeal(player);
} else if (transfer) { } else if (transfer) {
this.handleToggleTransfer(player); this.handleToggleTransfer(player);
} else if (this.transferTarget && item) { } else if (this.transferTarget && item) {
this.handleTransfer(item); this.handleTransfer(player, item);
} else if (item) { } else if (item) {
this.map.handleItemUse(player, item); this.map.handleItemUse(player, item);
} else if (player !== this.player) { } else if (player !== this.player) {
@ -137,16 +138,33 @@ export default class Inventory extends Entity {
} }
} }
private handleTransfer(item: Item) { private handleTransfer(player: Player, item: Item) {
if (this.transferTarget) { if (this.transferTarget) {
const itemExisted = this.player.removeItem(item); const itemExisted = player.removeItem(item);
if (itemExisted) { if (itemExisted) {
this.transferTarget.inventory.push(item); this.transferTarget.addItem(item);
} }
} }
this.transferTarget = undefined; 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) { private drawPlayer(ctx: CanvasRenderingContext2D, idx: number) {
const player = this.players[idx]; const player = this.players[idx];
@ -167,7 +185,7 @@ export default class Inventory extends Entity {
} }
let y = 2; let y = 2;
for (const item of player.inventory) { for (const item of player) {
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;
} }
@ -191,9 +209,12 @@ export default class Inventory extends Entity {
ctx.scale(1 / this.width, 1 / this.height); ctx.scale(1 / this.width, 1 / this.height);
ctx.font = `${0.3 * columnWidth}px Arial`; ctx.font = `${0.3 * columnWidth}px Arial`;
const activeTiles = new Set(this.activeTiles.map(({ tile }) => tile));
this.transferButtons.slice(0, this.players.length) this.transferButtons.slice(0, this.players.length)
.map((tile, playerIdx) => ({ tile, player: this.players[playerIdx] })) .map((tile, playerIdx) => ({ tile, player: this.players[playerIdx] }))
.filter(({ player }) => this.map.canTransferTo(this.player, player)) .filter(({ tile }) => activeTiles.has(tile))
.forEach(({ tile, player }) => { .forEach(({ tile, player }) => {
if (player === this.transferTarget) { if (player === this.transferTarget) {
ctx.fillStyle = 'lime'; ctx.fillStyle = 'lime';
@ -205,12 +226,11 @@ export default class Inventory extends Entity {
ctx.fillStyle = 'black'; ctx.fillStyle = 'black';
this.healButtons.slice(0, this.players.length) this.healButtons.slice(0, this.players.length)
.map((tile, playerIdx) => ({ tile, player: this.players[playerIdx] })) .filter((tile) => activeTiles.has(tile))
.filter(({ player }) => this.map.canHeal(this.player, player)) .forEach((tile) => {
.forEach(({ tile }) => {
ctx.fillText('🩹', tile.centerX, tile.centerY); ctx.fillText('🩹', tile.centerX, tile.centerY);
}); });
this.activeTiles.forEach(({ tile }) => tile.render(ctx)); activeTiles.forEach((tile) => tile.render(ctx));
} }
} }

View File

@ -5,10 +5,11 @@ import Tile from "./tile";
export default class Player extends Character { export default class Player extends Character {
public health: number; public health: number;
public inventory: Item[] = [];
public lastDoor: Tile | undefined; public lastDoor: Tile | undefined;
public active = true; public active = true;
private inventory: Item[] = [];
constructor(type: ItemTypeImage) { constructor(type: ItemTypeImage) {
super(type); super(type);
this.health = this.maxHealth; this.health = this.maxHealth;
@ -68,8 +69,18 @@ export default class Player extends Character {
return this.inventory.find(i => i.type === ItemType.ITEM_HEALING_KIT); return this.inventory.find(i => i.type === ItemType.ITEM_HEALING_KIT);
} }
public heal(player: Player) { public heal(patient: Player) {
player.health += this.healingAmount; patient.health += this.healingAmount;
}
public addItem(item: Item | null | undefined) {
if (item) {
this.inventory.push(item);
}
}
public getItem(i: number) {
return this.inventory[i];
} }
public removeItem(item: Item | null | undefined): boolean { public removeItem(item: Item | null | undefined): boolean {
@ -86,6 +97,12 @@ export default class Player extends Character {
return false; return false;
} }
public removeAllItems(): Item[] {
const inventory = this.inventory.slice();
this.inventory = [];
return inventory;
}
public hasItem(item: Item | ItemTypeImage | null | undefined): boolean { public hasItem(item: Item | ItemTypeImage | null | undefined): boolean {
if (!item) { if (!item) {
return false; return false;
@ -95,6 +112,18 @@ export default class Player extends Character {
return itemIndex >= 0; return itemIndex >= 0;
} }
public get hasAnyItems() {
return this.inventory.length > 0;
}
public get numItems() {
return this.inventory.length;
}
public [Symbol.iterator]() {
return this.inventory.slice()[Symbol.iterator]();
}
public toggleActive() { public toggleActive() {
this.active = !this.active; this.active = !this.active;
} }

View File

@ -14,9 +14,10 @@ export enum TileType {
export default class Tile extends Entity { export default class Tile extends Entity {
public connections: Tile[] = []; public connections: Tile[] = [];
public items: Item[] = [];
public isOpen = false; public isOpen = false;
private items: Item[] = [];
constructor(position: [number, number], size: number, public type: TileType = TileType.NORMAL) { constructor(position: [number, number], size: number, public type: TileType = TileType.NORMAL) {
super(position, [size, size]); super(position, [size, size]);
} }
@ -101,6 +102,12 @@ export default class Tile extends Entity {
this.isOpen = true; this.isOpen = true;
} }
public addItem(item: Item | null | undefined) {
if (item) {
this.items.push(item);
}
}
public removeItem(item: Item | null | undefined): boolean { public removeItem(item: Item | null | undefined): boolean {
if (!item) { if (!item) {
return false; return false;
@ -115,6 +122,18 @@ export default class Tile extends Entity {
return false; return false;
} }
public get hasSingleItem() {
return this.items.length === 1;
}
public get hasAnyItems() {
return this.items.length > 0;
}
public [Symbol.iterator]() {
return this.items.slice()[Symbol.iterator]();
}
public killEnemy() { public killEnemy() {
this.removeItem(this.enemy); this.removeItem(this.enemy);
} }

View File

@ -190,30 +190,28 @@ export default class TileMap extends Entity {
} }
const endTiles = this.tiles.filter(t => t.type === TileType.END); const endTiles = this.tiles.filter(t => t.type === TileType.END);
const endTilesNeighbors = new Set(endTiles.flatMap(t => t.connections));
const doorTiles = this.tiles.filter(t => t.type === TileType.DOOR || t.type === TileType.DOORWAY); const doorTiles = this.tiles.filter(t => t.type === TileType.DOOR || t.type === TileType.DOORWAY);
const normalTiles = this.tiles.filter(t => const normalTiles = this.tiles.filter(t =>
t.type === TileType.NORMAL t.type === TileType.NORMAL
&& !endTilesNeighbors.has(t)
&& !this.startTile.connections.includes(t) && !this.startTile.connections.includes(t)
); );
const fillableTiles = [ const fillableTiles = [
...endTilesNeighbors, ...endTiles,
...this.startTile.connections, ...this.startTile.connections,
...doorTiles, ...doorTiles,
...shuffle(normalTiles), ...shuffle(normalTiles),
]; ];
for (const [tile, item] of zip(fillableTiles, shuffle(items))) { for (const [tile, item] of zip(fillableTiles, shuffle(items))) {
tile.items.push(item); tile.addItem(item);
if (item instanceof Character) { if (item instanceof Character) {
item.tile = tile; item.tile = tile;
} }
} }
for (const tile of this.tiles) { for (const tile of this.tiles) {
if (tile.items.length === 0) { if (!tile.hasAnyItems) {
tile.isOpen = true; tile.isOpen = true;
} }
} }
@ -225,7 +223,7 @@ export default class TileMap extends Entity {
get activeTiles() { get activeTiles() {
return this.tiles.filter(tile => ( return this.tiles.filter(tile => (
tile.items.length > 0 tile.hasAnyItems
|| 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))
@ -237,20 +235,20 @@ export default class TileMap extends Entity {
(tile) => (tile) =>
tile.isOpen tile.isOpen
&& tile.enemy && tile.enemy
&& tile.items.length === 1 && tile.hasSingleItem
) )
} }
public canTransferTo(player: Player, otherPlayer: Player): boolean { public canTransferTo(player: Player, target: Player): boolean {
if (otherPlayer === player) return false; if (target === player) return false;
if (player.inventory.length === 0) return false; if (!player.hasAnyItems) return false;
if (player.tile === otherPlayer.tile) { if (player.tile === target.tile) {
return true; return true;
} }
for (const tile of player.tile.connections) { for (const tile of player.tile.connections) {
if (otherPlayer.tile === tile) { if (target.tile === tile) {
return true; return true;
} }
} }
@ -258,19 +256,10 @@ export default class TileMap extends Entity {
return false; return false;
} }
public canHeal(player: Player, otherPlayer: Player): boolean { public canHeal(healer: Player, patient: Player): boolean {
if (!player.healingKit) return false; if (!healer.healingKit) return false;
return player === otherPlayer || this.canTransferTo(player, otherPlayer); return healer === patient || this.canTransferTo(healer, patient);
}
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 {
@ -289,11 +278,11 @@ export default class TileMap extends Entity {
this.player.lastDoor = path.find(t => t.type === TileType.DOOR); this.player.lastDoor = path.find(t => t.type === TileType.DOOR);
this.player.moveTo(tile, path); this.player.moveTo(tile, path);
tile.open(); tile.open();
for (const item of tile.items.slice()) { // iterate remaining items for (const item of tile) {
if (item.isPickable) { if (item.isPickable) {
if (!tile.enemy) { if (!tile.enemy) {
tile.removeItem(item); tile.removeItem(item);
this.player.inventory.push(item); this.player.addItem(item);
} }
} else if (item instanceof Player) { } else if (item instanceof Player) {
tile.removeItem(item); tile.removeItem(item);
@ -339,7 +328,7 @@ export default class TileMap extends Entity {
switch (action) { switch (action) {
case SpinnerAction.BITE: case SpinnerAction.BITE:
if (this.player.isDead) { if (this.player.isDead) {
this.player.tile.items.push(...this.player.inventory); this.player.removeAllItems().forEach(i => this.player.tile.addItem(i));
this.players.splice(this.currentPlayerIdx, 1); this.players.splice(this.currentPlayerIdx, 1);
this.currentPlayerIdx = this.currentPlayerIdx % this.players.length; this.currentPlayerIdx = this.currentPlayerIdx % this.players.length;
this.setNormalState(); this.setNormalState();
@ -427,7 +416,7 @@ export default class TileMap extends Entity {
const moves = Pathfinding.findPossibleMoves(enemyTile, 5); const moves = Pathfinding.findPossibleMoves(enemyTile, 5);
for (const move of shuffle(moves)) { for (const move of shuffle(moves)) {
if (move.items.length > 0) continue; if (move.hasAnyItems) continue;
for (const [playerIndex, player] of enumerate(this.players)) { for (const [playerIndex, player] of enumerate(this.players)) {
if (player.tile === move) { if (player.tile === move) {
@ -442,7 +431,7 @@ export default class TileMap extends Entity {
const targetTile = path[allowedSteps] ?? player.tile; const targetTile = path[allowedSteps] ?? player.tile;
enemyTile.removeItem(enemy); enemyTile.removeItem(enemy);
targetTile.items.push(enemy); targetTile.addItem(enemy);
enemy.moveTo(targetTile, path.slice(0, allowedSteps + 1)); enemy.moveTo(targetTile, path.slice(0, allowedSteps + 1));
if (targetTile === player.tile) { if (targetTile === player.tile) {
@ -460,12 +449,12 @@ export default class TileMap extends Entity {
private killEnemy() { private killEnemy() {
this.player.tile.killEnemy(); this.player.tile.killEnemy();
this.player.tile.items.slice().forEach((item) => { for (const item of this.player.tile) {
if (item.isPickable) { if (item.isPickable) {
this.player.inventory.push(item); this.player.addItem(item);
this.player.tile.removeItem(item); this.player.tile.removeItem(item);
} }
}); };
this.nextPlayer(); this.nextPlayer();
this.setNormalState(); this.setNormalState();
} }
@ -476,7 +465,7 @@ export default class TileMap extends Entity {
} }
const endTiles = this.tiles.filter(t => t.type === TileType.END); const endTiles = this.tiles.filter(t => t.type === TileType.END);
for (const tile of endTiles) { for (const tile of endTiles) {
if (tile.items.length !== 0) return false; if (tile.hasAnyItems) return false;
} }
for (const player of Object.values(Players)) { for (const player of Object.values(Players)) {
if (!this.foundPlayers.has(player)) return false; if (!this.foundPlayers.has(player)) return false;
@ -539,7 +528,7 @@ export default class TileMap extends Entity {
for (const tile of this.tiles) { for (const tile of this.tiles) {
if (tile.type !== TileType.END) continue; if (tile.type !== TileType.END) continue;
this.drawItem(ctx, ItemType.ENEMY_ZOMBIE, x, y, w, tile.items.length === 0); this.drawItem(ctx, ItemType.ENEMY_ZOMBIE, x, y, w, !tile.hasAnyItems);
x += this.tileSize; x += this.tileSize;
} }
} }