'use strict'; // Browser: use window globals; Node: use require const _C2 = typeof require !== 'undefined' ? require('../core/constants') : { TILE_TYPES, ROWS, COLS, DIR_NAMES }; const _E = typeof require !== 'undefined' ? require('../data/enemies') : { ENEMY_TYPES, getEnemyType }; const _B = typeof require !== 'undefined' ? require('../data/bosses') : { BOSS_TYPES }; const _I = typeof require !== 'undefined' ? require('../data/items') : { ITEMS, getItem }; const _DIR_NAMES = _C2.DIR_NAMES; // Enemy pools by difficulty tier (cumulative) const ENEMY_TIERS = { 1: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite'], 2: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite'], 3: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma'], 4: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma'], 5: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma', 'moblin', 'stalfos', 'goriya', 'armos', 'manhandla'], 6: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma', 'moblin', 'stalfos', 'goriya', 'armos', 'manhandla'], 7: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma', 'moblin', 'stalfos', 'goriya', 'armos', 'manhandla', 'wizzrobe', 'ironknuckle', 'darknut'], 8: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma', 'moblin', 'stalfos', 'goriya', 'armos', 'manhandla', 'wizzrobe', 'ironknuckle', 'darknut'], 9: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma', 'moblin', 'stalfos', 'goriya', 'armos', 'manhandla', 'wizzrobe', 'ironknuckle', 'darknut', 'lynel'], 10: ['slime', 'keese', 'peahat', 'reedeath', 'bub', 'tektite', 'octorok', 'like', 'leever', 'skullwallma', 'moblin', 'stalfos', 'goriya', 'armos', 'manhandla', 'wizzrobe', 'ironknuckle', 'darknut', 'lynel'], }; // Room type -> max enemy count const ENEMY_COUNT_BY_TYPE = { normal: 3, treasure: 1, puzzle: 1, trap: 2, boss: 2, entrance: 1, corridor: 0, }; function findFloorPosition(room, rng) { const candidates = []; for (let y = 1; y < _C2.ROWS - 1; y++) { for (let x = 1; x < _C2.COLS - 1; x++) { if (room.tiles[y][x] === _C2.TILE_TYPES.FLOOR) { candidates.push({ x, y }); } } } if (candidates.length === 0) return null; return candidates[Math.floor(rng() * candidates.length)]; } function findFloorPositionExcluding(room, rng, occupied) { const candidates = []; for (let y = 1; y < _C2.ROWS - 1; y++) { for (let x = 1; x < _C2.COLS - 1; x++) { if (room.tiles[y][x] === _C2.TILE_TYPES.FLOOR) { const taken = occupied.some(p => p.x === x && p.y === y); if (!taken) { candidates.push({ x, y }); } } } } if (candidates.length === 0) return null; return candidates[Math.floor(rng() * candidates.length)]; } function getEnemyPool(difficulty) { const tier = Math.max(1, Math.min(10, difficulty)); const pool = ENEMY_TIERS[tier]; if (!pool) return ENEMY_TIERS[1]; return pool; } function getRupeeType(difficulty, rng) { const r = rng(); if (difficulty <= 2) { return r < 0.85 ? 'rupee_green' : 'rupee_blue'; } else if (difficulty <= 4) { if (r < 0.5) return 'rupee_green'; if (r < 0.9) return 'rupee_blue'; return 'rupee_red'; } else if (difficulty <= 6) { if (r < 0.3) return 'rupee_green'; if (r < 0.65) return 'rupee_blue'; if (r < 0.95) return 'rupee_red'; return 'rupee_yellow'; } else { if (r < 0.15) return 'rupee_green'; if (r < 0.4) return 'rupee_blue'; if (r < 0.75) return 'rupee_red'; return 'rupee_yellow'; } } function getRandomDirection(rng) { return _DIR_NAMES[Math.floor(rng() * 4)]; } function getEnemyCountForRoom(roomType, difficulty, rng) { const maxCount = ENEMY_COUNT_BY_TYPE[roomType] || 0; if (maxCount === 0) return 0; const chance = 0.6 + (difficulty / 10) * 0.3; if (rng() < (1 - chance)) return 0; return Math.floor(rng() * (maxCount + 1)); } function createEnemy(typeId, x, y, rng) { const def = _E.getEnemyType(typeId); if (!def) return null; return { id: def.id, x, y, hp: def.hp, maxHp: def.hp, alive: true, behavior: def.behavior, state: 'idle', direction: getRandomDirection(rng), timer: 0, damage: def.damage, defense: def.defense, speed: def.speed, attackRange: def.attackRange, }; } function createItem(typeId, x, y, count) { return { id: typeId, x, y, collected: false, count: count || 1, }; } function placeEnemies(rooms, dungeon, rng) { const difficulty = dungeon.difficulty || 1; const pool = getEnemyPool(difficulty); for (const room of rooms) { if (room.type === 'corridor') continue; const count = getEnemyCountForRoom(room.type, difficulty, rng); const occupied = []; for (let i = 0; i < count; i++) { const pos = findFloorPositionExcluding(room, rng, occupied); if (!pos) break; const typeId = pool[Math.floor(rng() * pool.length)]; const enemy = createEnemy(typeId, pos.x, pos.y, rng); if (enemy) { room.enemies.push(enemy); occupied.push({ x: pos.x, y: pos.y }); } } } } function placeItems(rooms, dungeon, rng) { const difficulty = dungeon.difficulty || 1; const hasBow = dungeon.playerHasBow || false; for (const room of rooms) { if (room.type === 'corridor') continue; const occupied = room.enemies.map(e => ({ x: e.x, y: e.y })); // Rupees: 30% chance per normal room, 0-3 rupees if (room.type === 'normal' || room.type === 'entrance') { if (rng() < 0.3) { const count = 1 + Math.floor(rng() * 3); for (let i = 0; i < count; i++) { const pos = findFloorPositionExcluding(room, rng, occupied); if (!pos) break; const rupeeType = getRupeeType(difficulty, rng); room.items.push(createItem(rupeeType, pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } } // Healing potions: 15% chance per room if (rng() < 0.15) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { room.items.push(createItem('healing_potion', pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } // Super potions: 5% chance (difficulty 3+) if (difficulty >= 3 && rng() < 0.05) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { room.items.push(createItem('super_potion', pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } // Magic potions: 3% chance (difficulty 5+) if (difficulty >= 5 && rng() < 0.03) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { room.items.push(createItem('magic_potion', pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } // Arrows: 20% chance if player has bow if (hasBow && rng() < 0.2) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { const arrowCount = 3 + Math.floor(rng() * 5); room.items.push(createItem('arrow', pos.x, pos.y, arrowCount)); occupied.push({ x: pos.x, y: pos.y }); } } // Bombs: 15% chance per room if (rng() < 0.15) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { const bombCount = 1 + Math.floor(rng() * 4); room.items.push(createItem('bomb', pos.x, pos.y, bombCount)); occupied.push({ x: pos.x, y: pos.y }); } } // Small keys: placed in treasure rooms or random rooms if (room.type === 'treasure') { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { room.items.push(createItem('small_key', pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } else if (rng() < 0.08) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { room.items.push(createItem('small_key', pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } // Heart pieces: 2% chance per room (rare) if (rng() < 0.02) { const pos = findFloorPositionExcluding(room, rng, occupied); if (pos) { room.items.push(createItem('heart_piece', pos.x, pos.y, 1)); occupied.push({ x: pos.x, y: pos.y }); } } } } function placeBoss(bossRoom, bossType, rng) { const def = _B.BOSS_TYPES[bossType]; if (!def) return null; const x = Math.floor(_C2.COLS / 2); const y = Math.floor(_C2.ROWS / 2); const boss = { id: def.id, name: def.name, x, y, hp: def.hp, maxHp: def.hp, damage: def.damage, defense: def.defense, behavior: def.behavior, phases: def.phases ? def.phases.map(p => ({ ...p, attacks: p.attacks.map(a => ({ ...a })) })) : [], alive: true, drops: def.drops ? [...def.drops] : [], currentState: { phase: 0, attackTimer: {}, stunned: false, frozen: false, }, direction: getRandomDirection(rng), timer: 0, state: 'idle', }; return boss; } function generatePlacement(rooms, dungeon, rng) { placeEnemies(rooms, dungeon, rng); placeItems(rooms, dungeon, rng); // Place boss in the boss room const bossRoom = rooms.find(r => r.type === 'boss'); if (bossRoom && dungeon.bossId !== undefined) { const boss = placeBoss(bossRoom, dungeon.bossId, rng); if (boss) { bossRoom.boss = boss; bossRoom.hasBoss = true; } } } if (typeof module !== 'undefined' && module.exports) { module.exports = { generatePlacement, placeEnemies, placeItems, placeBoss, findFloorPosition, getEnemyPool, getRupeeType, }; }