5cf42a5e5d
Frontend: - HTML5 Canvas isometric tilemap renderer - Eichenhafen starting city (procedural) - Click-to-move A* pathfinding - Player stats, HP/SP/EXP bars, skill bar, minimap - 3 NPCs (merchant, class master, innkeeper) - 6 monster spawns with aggro AI - Combat: auto-attack, damage numbers, EXP/gold - Player death + respawn Backend: - FastAPI server with SQLite - Ollama LLM bridge for events + dialogue - Event validation (no soft-lock) - NPC dialogue generation Design: - Full DESIGN.md with architecture, formulas, milestones - 6 base classes + 6 advanced classes - 3 fixed cities, dynamic events
85 lines
2.7 KiB
JavaScript
85 lines
2.7 KiB
JavaScript
// input.js — Mouse and keyboard input handling
|
|
|
|
class InputHandler {
|
|
constructor(canvas, game) {
|
|
this.canvas = canvas;
|
|
this.game = game;
|
|
this.mouseX = 0;
|
|
this.mouseY = 0;
|
|
|
|
canvas.addEventListener('click', (e) => this.handleClick(e));
|
|
canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
|
canvas.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
|
|
document.addEventListener('keydown', (e) => this.handleKey(e));
|
|
}
|
|
|
|
handleClick(e) {
|
|
const rect = this.canvas.getBoundingClientRect();
|
|
const mx = e.clientX - rect.left;
|
|
const my = e.clientY - rect.top;
|
|
|
|
// Convert screen to world coords
|
|
const worldX = mx + this.game.camera.x - 480;
|
|
const worldY = my + this.game.camera.y - 320;
|
|
|
|
// Convert to tile coords
|
|
const tile = this.game.map.screenToTile(worldX, worldY);
|
|
|
|
// Check if clicking a monster
|
|
for (const monster of this.game.monsters) {
|
|
if (monster.tileX === tile.x && monster.tileY === tile.y) {
|
|
this.game.player.attackTarget = monster;
|
|
this.game.addEvent(`Targeting ${monster.name}`, 'combat');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if clicking an NPC
|
|
for (const npc of this.game.npcs) {
|
|
if (npc.tileX === tile.x && npc.tileY === tile.y) {
|
|
this.game.interactNPC(npc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Otherwise, move to clicked tile
|
|
if (!this.game.map.isSolid(tile.x, tile.y)) {
|
|
const playerTile = { x: Math.floor(this.game.player.x), y: Math.floor(this.game.player.y) };
|
|
const path = this.game.findPath(playerTile, tile);
|
|
if (path) {
|
|
this.game.player.setPath(path);
|
|
this.game.player.attackTarget = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
handleMouseMove(e) {
|
|
const rect = this.canvas.getBoundingClientRect();
|
|
this.mouseX = e.clientX - rect.left;
|
|
this.mouseY = e.clientY - rect.top;
|
|
}
|
|
|
|
handleKey(e) {
|
|
// Skill bar (1-0)
|
|
if (e.key >= '0' && e.key <= '9') {
|
|
const slot = e.key === '0' ? 10 : parseInt(e.key);
|
|
this.game.useSkill(slot);
|
|
}
|
|
|
|
// Other keys
|
|
switch(e.key) {
|
|
case 'i':
|
|
case 'I':
|
|
this.game.toggleInventory();
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
this.game.toggleQuestLog();
|
|
break;
|
|
case 'Escape':
|
|
this.game.closeMenus();
|
|
break;
|
|
}
|
|
}
|
|
} |