v0.1.0 — M1: Project Setup + Tilemap + Combat + Backend
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
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user