Implement manual player movement planning (steps) and hopping animation
- GameEngine: Added path planning logic (click to add step, re-click to undo). - GameRenderer: Added path visualization (numbered yellow squares). - GameRenderer: Updated animation to include 'hopping' effect and clear path markers on visit. - UIManager: Replaced alerts with modals. - Main: Wired right-click to execute movement.
This commit is contained in:
@@ -9,6 +9,7 @@ export class GameEngine {
|
||||
this.player = null;
|
||||
this.selectedEntity = null;
|
||||
this.isRunning = false;
|
||||
this.plannedPath = []; // Array of {x,y}
|
||||
|
||||
// Callbacks
|
||||
this.onEntityUpdate = null;
|
||||
@@ -39,54 +40,105 @@ export class GameEngine {
|
||||
if (this.onEntityUpdate) {
|
||||
this.onEntityUpdate(this.player);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
onCellClick(x, y) {
|
||||
// If no player selected, select player on click
|
||||
if (!this.selectedEntity && this.player && x === this.player.x && y === this.player.y) {
|
||||
this.selectedEntity = this.player;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(this.player.id, true);
|
||||
// 1. SELECT / DESELECT PLAYER
|
||||
if (this.player && x === this.player.x && y === this.player.y) {
|
||||
if (this.selectedEntity === this.player) {
|
||||
// Toggle Deselect
|
||||
this.deselectPlayer();
|
||||
} else {
|
||||
// Select
|
||||
this.selectedEntity = this.player;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(this.player.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If player selected, move to clicked cell
|
||||
// 2. PLAN MOVEMENT (If player selected)
|
||||
if (this.selectedEntity === this.player) {
|
||||
if (this.canMoveTo(x, y)) {
|
||||
this.movePlayer(x, y);
|
||||
} else {
|
||||
this.planStep(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
deselectPlayer() {
|
||||
this.selectedEntity = null;
|
||||
this.plannedPath = [];
|
||||
if (this.onEntitySelect) this.onEntitySelect(this.player.id, false);
|
||||
if (this.onPathChange) this.onPathChange([]);
|
||||
}
|
||||
|
||||
planStep(x, y) {
|
||||
// Determine start point (either current player pos or last planned step)
|
||||
const lastStep = this.plannedPath.length > 0
|
||||
? this.plannedPath[this.plannedPath.length - 1]
|
||||
: { x: this.player.x, y: this.player.y };
|
||||
|
||||
// Check Adjacency
|
||||
const dx = Math.abs(x - lastStep.x);
|
||||
const dy = Math.abs(y - lastStep.y);
|
||||
const isAdjacent = (dx + dy) === 1;
|
||||
|
||||
// Check Walkability
|
||||
const isWalkable = this.canMoveTo(x, y);
|
||||
|
||||
// Check if already in path (prevent loops for simplicity or allow backtracking? User said "mark contigua", implying adding)
|
||||
// If clicking the last added step, maybe remove it? (Undo)
|
||||
if (this.plannedPath.length > 0 && x === lastStep.x && y === lastStep.y) {
|
||||
// Clicked last step -> Undo
|
||||
this.plannedPath.pop();
|
||||
if (this.onPathChange) this.onPathChange(this.plannedPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAdjacent && isWalkable) {
|
||||
// Check if not already visited in this path to prevent self-intersection weirdness
|
||||
const alreadyInPath = this.plannedPath.some(p => p.x === x && p.y === y);
|
||||
const isPlayerPos = this.player.x === x && this.player.y === y;
|
||||
|
||||
if (!alreadyInPath && !isPlayerPos) {
|
||||
this.plannedPath.push({ x, y });
|
||||
if (this.onPathChange) {
|
||||
this.onPathChange(this.plannedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeMovePath() {
|
||||
if (!this.player || !this.plannedPath.length) return;
|
||||
|
||||
// Clone path for the move event
|
||||
const path = [...this.plannedPath];
|
||||
|
||||
// Update player logic verification immediately (teleport logic)
|
||||
// The visualization will handle the "botecitos"
|
||||
const finalDest = path[path.length - 1];
|
||||
this.player.x = finalDest.x;
|
||||
this.player.y = finalDest.y;
|
||||
|
||||
// Trigger Movement Event (Renderer will animate)
|
||||
if (this.onEntityMove) {
|
||||
this.onEntityMove(this.player, path);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
this.deselectPlayer();
|
||||
}
|
||||
|
||||
canMoveTo(x, y) {
|
||||
// Check if cell is walkable (occupied by a tile)
|
||||
return this.dungeon.grid.isOccupied(x, y);
|
||||
}
|
||||
|
||||
// Deprecated direct move
|
||||
movePlayer(x, y) {
|
||||
// Simple direct movement (no pathfinding for now)
|
||||
const path = [{ x, y }];
|
||||
|
||||
this.player.x = x;
|
||||
this.player.y = y;
|
||||
|
||||
if (this.onEntityMove) {
|
||||
this.onEntityMove(this.player, path);
|
||||
}
|
||||
|
||||
// Deselect after move
|
||||
this.selectedEntity = null;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(this.player.id, false);
|
||||
}
|
||||
|
||||
|
||||
if (this.onEntityMove) this.onEntityMove(this.player, [{ x, y }]);
|
||||
}
|
||||
|
||||
isPlayerAdjacentToDoor(doorCells) {
|
||||
|
||||
Reference in New Issue
Block a user