feat: Implement 2D tactical view and refine LOS with corner detection
This commit is contained in:
@@ -823,25 +823,76 @@ export class GameEngine {
|
||||
break;
|
||||
}
|
||||
|
||||
// Helper: Distance from Cell Center to Ray (for grazing tolerance)
|
||||
const getDist = () => {
|
||||
const cx = currentX + 0.5;
|
||||
const cy = currentY + 0.5;
|
||||
const len = Math.sqrt(dx * dx + dy * dy);
|
||||
if (len === 0) return 0;
|
||||
return Math.abs(dy * cx - dx * cy + dx * y1 - dy * x1) / len;
|
||||
};
|
||||
|
||||
// Tolerance: Allow shots to pass if they graze the edge (0.5 is full width)
|
||||
// 0.4 means the outer 20% of the tile is "safe" to shoot through.
|
||||
const ENTITY_HITBOX_RADIUS = 0.4;
|
||||
|
||||
// 2. Monster Check
|
||||
const m = this.monsters.find(m => m.x === currentX && m.y === currentY && !m.isDead && m.id !== target.id);
|
||||
if (m) {
|
||||
blocked = true;
|
||||
blocker = { type: 'monster', entity: m };
|
||||
console.log(`[LOS] Blocked by MONSTER: ${m.name}`);
|
||||
break;
|
||||
if (getDist() < ENTITY_HITBOX_RADIUS) {
|
||||
blocked = true;
|
||||
blocker = { type: 'monster', entity: m };
|
||||
console.log(`[LOS] Blocked by MONSTER: ${m.name}`);
|
||||
break;
|
||||
} else {
|
||||
console.log(`[LOS] Grazed MONSTER ${m.name} (Dist: ${getDist().toFixed(2)})`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Hero Check
|
||||
const h = this.heroes.find(h => h.x === currentX && h.y === currentY && h.id !== hero.id);
|
||||
if (h) {
|
||||
blocked = true;
|
||||
blocker = { type: 'hero', entity: h };
|
||||
console.log(`[LOS] Blocked by HERO: ${h.name}`);
|
||||
break;
|
||||
if (getDist() < ENTITY_HITBOX_RADIUS) {
|
||||
blocked = true;
|
||||
blocker = { type: 'hero', entity: h };
|
||||
console.log(`[LOS] Blocked by HERO: ${h.name}`);
|
||||
break;
|
||||
} else {
|
||||
console.log(`[LOS] Grazed HERO ${h.name} (Dist: ${getDist().toFixed(2)})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentX === endX && currentY === endY) break;
|
||||
|
||||
// CORNER CROSSING CHECK: Prevent diagonal wall leaking
|
||||
// When tMaxX ≈ tMaxY, the ray passes through a vertex shared by 4 cells.
|
||||
// Standard algorithm only visits 2 of them. We must check BOTH neighbors.
|
||||
const CORNER_EPSILON = 0.001;
|
||||
const cornerCrossing = Math.abs(tMaxX - tMaxY) < CORNER_EPSILON;
|
||||
|
||||
if (cornerCrossing) {
|
||||
// Check both orthogonal neighbors
|
||||
const neighborX = currentX + stepX;
|
||||
const neighborY = currentY + stepY;
|
||||
|
||||
// Check horizontal neighbor
|
||||
if (this.dungeon.grid.isWall(neighborX, currentY)) {
|
||||
blocked = true;
|
||||
blocker = { type: 'wall', x: neighborX, y: currentY };
|
||||
console.log(`[LOS] Blocked by CORNER WALL at ${neighborX},${currentY}`);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check vertical neighbor
|
||||
if (this.dungeon.grid.isWall(currentX, neighborY)) {
|
||||
blocked = true;
|
||||
blocker = { type: 'wall', x: currentX, y: neighborY };
|
||||
console.log(`[LOS] Blocked by CORNER WALL at ${currentX},${neighborY}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tMaxX < tMaxY) {
|
||||
tMaxX += tDeltaX;
|
||||
currentX += stepX;
|
||||
|
||||
Reference in New Issue
Block a user