172 lines
7.1 KiB
JavaScript
172 lines
7.1 KiB
JavaScript
/**
|
|
* DungeonSystem — wraps ContentLoader for dungeon/enemy defs + manages instances.
|
|
*/
|
|
class DungeonSystem {
|
|
constructor(contentLoader) {
|
|
this.loader = contentLoader;
|
|
this.io = null;
|
|
this.instances = new Map(); // instanceId -> instance
|
|
this.playerInstances = new Map(); // userId -> instanceId
|
|
|
|
this.roomTypes = {
|
|
entrance: { name: 'Entrance', enemies: 0, rewards: false },
|
|
corridor: { name: 'Corridor', enemies: 1, rewards: false },
|
|
chamber: { name: 'Chamber', enemies: 2, rewards: true },
|
|
treasure: { name: 'Treasure Room',enemies: 0, rewards: true },
|
|
boss: { name: 'Boss Room', enemies: 1, rewards: true, isBoss: true },
|
|
exit: { name: 'Exit', enemies: 0, rewards: false }
|
|
};
|
|
}
|
|
|
|
setIO(io) { this.io = io; }
|
|
|
|
// ── Content queries (delegated to ContentLoader) ──────────────────
|
|
getAllDungeons() { return this.loader.getAllDungeons(); }
|
|
getDungeon(id) { return this.loader.getDungeon(id); }
|
|
getDungeonsByDifficulty(d) { return this.loader.getDungeonsByDifficulty(d); }
|
|
getDungeonsGroupedByDifficulty() { return this.loader.getDungeonsGroupedByDifficulty(); }
|
|
getAvailableDungeons(lvl) { return this.loader.getAvailableDungeons(lvl); }
|
|
getEnemyTemplates() { return this.loader.getAllEnemies(); }
|
|
getEnemy(id) { return this.loader.getEnemy(id); }
|
|
|
|
// ── Instance management ───────────────────────────────────────────
|
|
createInstance(dungeonId, userId, _partyIds = []) {
|
|
const def = this.loader.getDungeon(dungeonId);
|
|
if (!def) return null;
|
|
|
|
const [minR, maxR] = def.roomCount || [4, 6];
|
|
const roomCount = Math.floor(Math.random() * (maxR - minR + 1)) + minR;
|
|
const instanceId = `${userId}_${dungeonId}_${Date.now()}`;
|
|
|
|
const instance = {
|
|
instanceId,
|
|
dungeonId,
|
|
userId,
|
|
startTime: Date.now(),
|
|
currentRoom: 0,
|
|
totalRooms: roomCount,
|
|
rooms: this._generateRooms(def, roomCount),
|
|
completed: false,
|
|
failed: false
|
|
};
|
|
|
|
this.instances.set(instanceId, instance);
|
|
this.playerInstances.set(userId, instanceId);
|
|
return instance;
|
|
}
|
|
|
|
getPlayerInstance(userId) {
|
|
const id = this.playerInstances.get(userId);
|
|
return id ? this.instances.get(id) || null : null;
|
|
}
|
|
|
|
getPlayerCompletedDungeons(_userId) {
|
|
// Could be persisted to DB; return empty array for now
|
|
return [];
|
|
}
|
|
|
|
startEncounter(instanceId, userId) {
|
|
const instance = this.instances.get(instanceId);
|
|
if (!instance) return { success: false, error: 'Instance not found' };
|
|
const room = instance.rooms[instance.currentRoom];
|
|
if (!room) return { success: false, error: 'No more rooms' };
|
|
return { success: true, room, currentRoom: instance.currentRoom, totalRooms: instance.totalRooms };
|
|
}
|
|
|
|
completeEncounter(instanceId, userId, result = {}) {
|
|
const instance = this.instances.get(instanceId);
|
|
if (!instance) return { success: false, error: 'Instance not found' };
|
|
|
|
const room = instance.rooms[instance.currentRoom];
|
|
if (room) room.cleared = true;
|
|
instance.currentRoom++;
|
|
|
|
const isComplete = instance.currentRoom >= instance.totalRooms;
|
|
const rewards = isComplete ? this._generateRewards(instance.dungeonId) : [];
|
|
if (isComplete) instance.completed = true;
|
|
|
|
return { success: true, room, rewards, completed: isComplete };
|
|
}
|
|
|
|
moveToNextRoom(instanceId, userId) {
|
|
const instance = this.instances.get(instanceId);
|
|
if (!instance) return { success: false, error: 'Instance not found' };
|
|
const room = instance.rooms[instance.currentRoom];
|
|
return { success: true, room: room || null, currentRoom: instance.currentRoom, totalRooms: instance.totalRooms };
|
|
}
|
|
|
|
completeDungeon(instanceId) {
|
|
const instance = this.instances.get(instanceId);
|
|
if (!instance) return { success: false, error: 'Instance not found' };
|
|
|
|
const rewards = this._generateRewards(instance.dungeonId);
|
|
const def = this.loader.getDungeon(instance.dungeonId);
|
|
|
|
this.instances.delete(instanceId);
|
|
this.playerInstances.delete(instance.userId);
|
|
|
|
return { success: true, rewards, dungeonName: def?.name, timeMs: Date.now() - instance.startTime };
|
|
}
|
|
|
|
// ── Internals ─────────────────────────────────────────────────────
|
|
_generateRooms(def, count) {
|
|
const rooms = [];
|
|
const enemyPool = def.enemyPool || [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const isFirst = i === 0;
|
|
const isLast = i === count - 1;
|
|
const isBoss = i === count - 2 && count > 2;
|
|
|
|
let type = 'corridor', enemies = [];
|
|
|
|
if (isFirst) type = 'entrance';
|
|
else if (isLast) type = 'exit';
|
|
else if (isBoss) {
|
|
type = 'boss';
|
|
const bossId = enemyPool[enemyPool.length - 1] || enemyPool[0];
|
|
const tmpl = this.loader.getEnemy(bossId);
|
|
if (tmpl) enemies.push({ ...tmpl, health: Math.round(tmpl.health * 2), attack: Math.round(tmpl.attack * 1.5) });
|
|
} else {
|
|
const roll = Math.random();
|
|
if (roll < 0.2) type = 'treasure';
|
|
else if (roll < 0.5) type = 'chamber';
|
|
else type = 'corridor';
|
|
|
|
const ec = this.roomTypes[type]?.enemies || 0;
|
|
for (let e = 0; e < ec; e++) {
|
|
const eid = enemyPool[Math.floor(Math.random() * enemyPool.length)];
|
|
const tmpl = this.loader.getEnemy(eid);
|
|
if (tmpl) enemies.push({ ...tmpl });
|
|
}
|
|
}
|
|
|
|
rooms.push({ index: i, type, enemies, cleared: false, rewards: [] });
|
|
}
|
|
return rooms;
|
|
}
|
|
|
|
_generateRewards(dungeonId) {
|
|
const def = this.loader.getDungeon(dungeonId);
|
|
if (!def?.lootTable) return [];
|
|
|
|
const rewards = [];
|
|
const totalWeight = def.lootTable.reduce((s, e) => s + (e.weight || 0), 0);
|
|
const picks = Math.floor(Math.random() * 3) + 1;
|
|
|
|
for (let i = 0; i < picks; i++) {
|
|
let roll = Math.random() * totalWeight;
|
|
for (const entry of def.lootTable) {
|
|
roll -= entry.weight;
|
|
if (roll <= 0) {
|
|
const qty = Math.floor(Math.random() * (entry.qtyMax - entry.qtyMin + 1)) + entry.qtyMin;
|
|
rewards.push({ itemId: entry.itemId, quantity: qty });
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return rewards;
|
|
}
|
|
}
|
|
module.exports = DungeonSystem;
|