Skip to main content
Glama
dungeon.ts20.1 kB
import { CryptoStorage } from '../storage/crypto.js'; import { getAllDungeons, getDungeonById } from '../data/dungeons.js'; import { calculateTotalStats, simulateCombat, calculateDungeonTime } from '../game/combat.js'; import { rollLoot, cloneEquipment } from '../game/loot.js'; import { rollForEvent, formatEventDescription } from '../game/events.js'; import { cloneItem } from '../data/items.js'; import type { Dungeon, BattleLogEntry, Item } from '../types.js'; const storage = new CryptoStorage(); export async function listDungeons(saveKey: string): Promise<string> { const data = await storage.load(saveKey); if (!data.player.name) { return "プレイヤーが見つかりません。先に'create_player'を実行してください。"; } const dungeons = getAllDungeons(); const playerStats = calculateTotalStats(data.player.equipment); let output = `=== 利用可能なダンジョン ===\n\n`; for (const dungeon of dungeons) { const actualTime = calculateDungeonTime(dungeon.baseTime, playerStats.speed); output += `[${dungeon.id}] ${dungeon.name}\n`; output += ` 階層: ${dungeon.floors}階\n`; output += ` 所要時間: ${actualTime}分 (基本: ${dungeon.baseTime}分)\n`; output += ` 出現モンスター: ${dungeon.enemies.map(e => e.name).join(', ')}\n`; output += ` ボス: ${dungeon.boss.name}\n`; output += ` 報酬レアリティ: ${[...new Set(dungeon.rewardPool.map(r => r.rarity))].join(', ')}\n`; output += '\n'; } output += `詳細情報: 'dungeon_info <dungeon_id>'\n`; output += `探索開始: 'start_dungeon <dungeon_id>'`; return output; } export async function dungeonInfo(dungeonId: string, saveKey: string): Promise<string> { const data = await storage.load(saveKey); if (!data.player.name) { return "プレイヤーが見つかりません。先に'create_player'を実行してください。"; } const dungeon = getDungeonById(dungeonId); if (!dungeon) { return `ダンジョン '${dungeonId}' が見つかりません。`; } const playerStats = calculateTotalStats(data.player.equipment); const actualTime = calculateDungeonTime(dungeon.baseTime, playerStats.speed); let output = `=== ${dungeon.name} ===\n\n`; output += `階層: ${dungeon.floors}階\n`; output += `推定所要時間: ${actualTime}分\n\n`; output += `出現モンスター:\n`; for (const enemy of dungeon.enemies) { output += ` ${enemy.name}\n`; output += ` 攻撃: ${enemy.stats.attack} 防御: ${enemy.stats.defense} 速度: ${enemy.stats.speed} 運: ${enemy.stats.luck}\n`; output += ` ゴールド: ${enemy.goldDrop[0]}-${enemy.goldDrop[1]}\n`; output += ` ドロップ率: ${(enemy.equipmentDropRate * 100).toFixed(1)}%\n`; } output += `\nボス:\n`; output += ` ${dungeon.boss.name}\n`; output += ` 攻撃: ${dungeon.boss.stats.attack} 防御: ${dungeon.boss.stats.defense} 速度: ${dungeon.boss.stats.speed} 運: ${dungeon.boss.stats.luck}\n`; output += ` ゴールド: ${dungeon.boss.goldDrop[0]}-${dungeon.boss.goldDrop[1]}\n`; output += ` ドロップ率: ${(dungeon.boss.equipmentDropRate * 100).toFixed(1)}%\n`; output += `\n入手可能な装備:\n`; const rarities = [...new Set(dungeon.rewardPool.map(r => r.rarity))]; for (const rarity of rarities) { const items = dungeon.rewardPool.filter(r => r.rarity === rarity); output += ` ${rarity}: ${items.length}種類\n`; } return output; } export async function startDungeon(dungeonId: string, saveKey: string): Promise<string> { const data = await storage.load(saveKey); if (!data.player.name) { return "プレイヤーが見つかりません。先に'create_player'を実行してください。"; } if (data.player.currentDungeon) { const remaining = Math.max(0, data.player.currentDungeon.estimatedEndTime - Date.now()); const minutes = Math.ceil(remaining / 60000); return `既にダンジョンを探索中です!\n残り時間: 約${minutes}分\n\n'check_progress'で進行状況を確認できます。`; } const dungeon = getDungeonById(dungeonId); if (!dungeon) { return `ダンジョン '${dungeonId}' が見つかりません。`; } const playerStats = calculateTotalStats(data.player.equipment); const actualTime = calculateDungeonTime(dungeon.baseTime, playerStats.speed); const startTime = Date.now(); const estimatedEndTime = startTime + (actualTime * 60 * 1000); data.player.currentDungeon = { dungeonId, startTime, estimatedEndTime, battleLog: [], events: [] }; data.player.state = 'exploring'; await storage.save(data, saveKey); return `🗡️ ${dungeon.name}の探索を開始しました!\n\n推定完了時刻: ${actualTime}分後\n\n'check_progress'で進行状況を確認できます。`; } export async function checkProgress(saveKey: string): Promise<string> { const data = await storage.load(saveKey); if (!data.player.name) { return "プレイヤーが見つかりません。先に'create_player'を実行してください。"; } if (!data.player.currentDungeon) { return "現在探索中のダンジョンはありません。\n\n'list_dungeons'で利用可能なダンジョンを確認できます。"; } const now = Date.now(); const { dungeonId, startTime, estimatedEndTime } = data.player.currentDungeon; const remaining = estimatedEndTime - now; if (remaining > 0) { // 探索中 - リアルタイム進行状況を計算 const dungeon = getDungeonById(dungeonId); if (!dungeon) { return "エラー: ダンジョンデータが見つかりません。"; } // 経過時間と進行度を計算 const totalTime = estimatedEndTime - startTime; const elapsed = now - startTime; const progressPercentage = Math.min((elapsed / totalTime) * 100, 100); // 現在の階層を推定(線形補間) const currentFloor = Math.floor((dungeon.floors * progressPercentage) / 100); const displayFloor = Math.max(1, Math.min(currentFloor, dungeon.floors)); // 残り時間表示 const minutes = Math.ceil(remaining / 60000); const seconds = Math.ceil((remaining % 60000) / 1000); let output = `⚔️ ${dungeon.name}を探索中...\n\n`; output += `進行状況: ${progressPercentage.toFixed(1)}%\n`; output += `推定現在地: ${displayFloor}階 / ${dungeon.floors}階\n`; output += `残り時間: ${minutes}分${seconds}秒\n\n`; // プログレスバー const barLength = 20; const filledLength = Math.floor((progressPercentage / 100) * barLength); const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength); output += `[${bar}] ${progressPercentage.toFixed(0)}%\n\n`; // 装備している持ち物の状態 const playerStats = calculateTotalStats(data.player.equipment); const equippedHerb = data.player.equipment.item1?.type === 'herb' ? data.player.equipment.item1 : data.player.equipment.item2?.type === 'herb' ? data.player.equipment.item2 : undefined; const equippedCharm = data.player.equipment.item1?.type === 'charm' ? data.player.equipment.item1 : data.player.equipment.item2?.type === 'charm' ? data.player.equipment.item2 : undefined; if (equippedHerb || equippedCharm) { output += `持ち物状態:\n`; if (equippedHerb) { output += ` 🌿 ${equippedHerb.name}: 待機中\n`; } if (equippedCharm) { output += ` 🛡️ ${equippedCharm.name}: 待機中\n`; } output += '\n'; } // 探索中の活動概要(推定) output += `=== 探索状況 ===\n`; // 進行度に基づいて推定される活動 const estimatedBattles = Math.floor(displayFloor * 2); // 各階2戦想定 const estimatedEvents = Math.floor(displayFloor * 0.3); // 30%程度でイベント output += `推定戦闘数: 約${estimatedBattles}回\n`; output += `推定イベント: 約${estimatedEvents}回\n\n`; // 現在までの階層表示と推定バトルログ if (displayFloor > 1) { output += `通過した階層:\n`; const maxDisplay = Math.min(displayFloor, 5); // 最新5階まで表示 const startFloor = Math.max(1, displayFloor - maxDisplay + 1); for (let f = startFloor; f <= displayFloor; f++) { if (f === displayFloor) { output += ` → ${f}階 (探索中...)\n`; } else { output += ` ✓ ${f}階 (通過済み)\n`; } } if (displayFloor < dungeon.floors) { output += ` ... ${dungeon.floors - displayFloor}階が残っています\n`; } output += '\n'; // 推定バトルログ(最新3階分) output += `最近の戦闘(推定):\n`; const recentFloors = Math.min(3, displayFloor); const logStartFloor = Math.max(1, displayFloor - recentFloors + 1); for (let f = logStartFloor; f <= displayFloor; f++) { // ランダムに敵を選択(実際はシミュレーション) const enemy1 = dungeon.enemies[Math.floor(Math.random() * dungeon.enemies.length)]; const enemy2 = dungeon.enemies[Math.floor(Math.random() * dungeon.enemies.length)]; if (f === displayFloor) { output += ` ${f}階: ${enemy1.name}と交戦中...\n`; } else { output += ` ${f}階: ${enemy1.name}, ${enemy2.name}を撃破\n`; } } output += '\n'; } output += `💡 ヒント:\n`; output += `- 'view_status'でキャラクター情報を確認\n`; output += `- 完了後、もう一度'check_progress'で詳細な結果を確認\n`; output += `- 'view_battle_log'で完了後の戦闘ログを閲覧可能\n`; return output; } // ダンジョン完了 - 結果を処理 return await completeDungeon(saveKey); } async function completeDungeon(saveKey: string): Promise<string> { const data = await storage.load(saveKey); if (!data.player.currentDungeon) { return "進行中のダンジョンがありません。"; } const dungeon = getDungeonById(data.player.currentDungeon.dungeonId); if (!dungeon) { return "エラー: ダンジョンデータが見つかりません。"; } const playerStats = calculateTotalStats(data.player.equipment); let output = `=== ダンジョン攻略完了! ===\n\n`; output += `${dungeon.name}を制覇しました!\n\n`; // バトルログとイベントログ const battleLog: BattleLogEntry[] = []; const events = data.player.currentDungeon.events; let totalGold = 0; const allLoot: typeof data.player.inventory = []; const allItemLoot: Item[] = []; let currentFloor = 1; let totalDamageDealt = 0; let totalDamageTaken = 0; let totalCrits = 0; let totalDodges = 0; // 装備している持ち物を取得 const equippedHerb = data.player.equipment.item1?.type === 'herb' ? data.player.equipment.item1 : data.player.equipment.item2?.type === 'herb' ? data.player.equipment.item2 : undefined; const equippedCharm = data.player.equipment.item1?.type === 'charm' ? data.player.equipment.item1 : data.player.equipment.item2?.type === 'charm' ? data.player.equipment.item2 : undefined; let herbUsedTotal = false; let charmUsedTotal = false; // プレイヤーのHP管理 let playerCurrentHp = data.player.hp; const playerMaxHp = data.player.maxHp; // 階層ごとに進行 while (currentFloor <= dungeon.floors) { // イベント判定 const event = rollForEvent( currentFloor, playerStats.luck, dungeon.rewardPool, charmUsedTotal ? undefined : equippedCharm, dungeon.itemRewardPool ); if (event) { events.push(event); // おまもりが使用された場合 if (event.charmBlocked) { charmUsedTotal = true; } // イベント効果を適用 if (event.effect.goldChange) { totalGold += event.effect.goldChange; } if (event.effect.itemsGained) { allLoot.push(...event.effect.itemsGained.map(cloneEquipment)); } if (event.effect.holdingItemsGained) { allItemLoot.push(...event.effect.holdingItemsGained.map(cloneItem)); } if (event.effect.floorsSkipped) { currentFloor += event.effect.floorsSkipped; continue; } } // 通常の戦闘(各階2体) for (let i = 0; i < 2 && currentFloor <= dungeon.floors; i++) { const enemy = dungeon.enemies[Math.floor(Math.random() * dungeon.enemies.length)]; const combatResult = simulateCombat( playerStats, enemy, playerCurrentHp, playerMaxHp, herbUsedTotal ? undefined : equippedHerb, charmUsedTotal ? undefined : equippedCharm ); // 戦闘後のHPを更新 playerCurrentHp = combatResult.playerHpAfterBattle; // 薬草が使用された場合 if (combatResult.herbUsed) { herbUsedTotal = true; } // 復活が使用された場合 if (combatResult.revived) { charmUsedTotal = true; } const loot = rollLoot(enemy, playerStats.luck, dungeon.rewardPool); const battleEntry: BattleLogEntry = { floor: currentFloor, enemyName: enemy.name, victory: combatResult.victory, damageDealt: combatResult.damageDealt, damageTaken: combatResult.damageTaken, criticalHits: combatResult.criticalHits, dodges: combatResult.dodges, goldEarned: combatResult.victory ? loot.gold : 0, itemsDropped: combatResult.victory ? loot.equipment.map(cloneEquipment) : [], herbUsed: combatResult.herbUsed }; battleLog.push(battleEntry); if (combatResult.victory) { totalGold += loot.gold; allLoot.push(...loot.equipment.map(cloneEquipment)); totalDamageDealt += combatResult.damageDealt; totalDamageTaken += combatResult.damageTaken; totalCrits += combatResult.criticalHits; totalDodges += combatResult.dodges; } else { // 敗北した場合 output += `\n❌ ${currentFloor}階で${enemy.name}に敗北...\n\n`; output += `獲得したものの一部を失いました。\n`; totalGold = Math.floor(totalGold * 0.5); allLoot.length = Math.floor(allLoot.length * 0.5); data.player.gold += totalGold; data.player.inventory.push(...allLoot); // HP半分で復帰 data.player.hp = Math.floor(data.player.maxHp * 0.5); data.player.currentDungeon = undefined; data.player.state = 'idle'; await storage.save(data, saveKey); output += `\nゴールド: +${totalGold}\n`; output += `HP: ${data.player.hp}/${data.player.maxHp} (半分で復帰)\n`; return output; } } currentFloor++; } // ボス戦 output += `\n【ボス戦】 ${dungeon.boss.name}\n`; const bossCombat = simulateCombat( playerStats, dungeon.boss, playerCurrentHp, playerMaxHp, herbUsedTotal ? undefined : equippedHerb, charmUsedTotal ? undefined : equippedCharm ); // 戦闘後のHPを更新 playerCurrentHp = bossCombat.playerHpAfterBattle; if (bossCombat.herbUsed) { herbUsedTotal = true; } if (bossCombat.revived) { charmUsedTotal = true; } if (bossCombat.victory) { output += `✅ 勝利!\n`; output += ` 与ダメージ: ${bossCombat.damageDealt}\n`; output += ` 被ダメージ: ${bossCombat.damageTaken}\n`; output += ` クリティカル: ${bossCombat.criticalHits}回\n`; output += ` 回避: ${bossCombat.dodges}回\n`; if (bossCombat.herbUsed) { output += ` 薬草使用: ✅\n`; } if (bossCombat.revived) { output += ` 🌟 復活: ${equippedCharm?.name}により復活!\n`; } output += '\n'; const bossLoot = rollLoot(dungeon.boss, playerStats.luck, dungeon.rewardPool); totalGold += bossLoot.gold; allLoot.push(...bossLoot.equipment.map(cloneEquipment)); totalCrits += bossCombat.criticalHits; totalDodges += bossCombat.dodges; } else { output += `❌ 敗北...\n\n`; totalGold = Math.floor(totalGold * 0.7); allLoot.length = Math.floor(allLoot.length * 0.7); allItemLoot.length = Math.floor(allItemLoot.length * 0.7); } // 統計 output += `\n=== 戦闘統計 ===\n`; output += `総与ダメージ: ${totalDamageDealt}\n`; output += `総被ダメージ: ${totalDamageTaken}\n`; output += `クリティカル: ${totalCrits}回\n`; output += `回避: ${totalDodges}回\n`; // 報酬 data.player.gold += totalGold; data.player.inventory.push(...allLoot); data.player.itemInventory.push(...allItemLoot); // 使用済みの持ち物を装備から削除 if (herbUsedTotal && equippedHerb) { if (data.player.equipment.item1?.id === equippedHerb.id) { data.player.equipment.item1 = null; } else if (data.player.equipment.item2?.id === equippedHerb.id) { data.player.equipment.item2 = null; } } if (charmUsedTotal && equippedCharm) { if (data.player.equipment.item1?.id === equippedCharm.id) { data.player.equipment.item1 = null; } else if (data.player.equipment.item2?.id === equippedCharm.id) { data.player.equipment.item2 = null; } } // HPを更新(ダンジョン完了後は全回復) data.player.hp = data.player.maxHp; data.player.currentDungeon = undefined; data.player.state = 'idle'; await storage.save(data, saveKey); output += `\n=== 報酬 ===\n`; output += `HP: ${data.player.hp}/${data.player.maxHp} (全回復!)\n`; output += `ゴールド: +${totalGold} (合計: ${data.player.gold})\n`; if (allLoot.length > 0) { output += `装備:\n`; for (const item of allLoot) { output += ` - ${item.name} [${item.rarity}]\n`; } } else { output += `装備ドロップなし\n`; } if (allItemLoot.length > 0) { output += `持ち物:\n`; for (const item of allItemLoot) { output += ` - ${item.name}\n`; } } // 持ち物の使用状況 if (herbUsedTotal || charmUsedTotal) { output += `\n=== 持ち物使用 ===\n`; if (herbUsedTotal) { output += `薬草: 使用済み (HP回復)\n`; } if (charmUsedTotal) { output += `おまもり: 使用済み (災難回避)\n`; } } if (events.length > 0) { output += `\n=== 発生イベント ===\n`; for (const event of events) { output += formatEventDescription(event) + '\n'; } } output += `\n'view_inventory'で新しいアイテムを確認できます!`; output += `\n'view_battle_log'で詳細な戦闘ログを確認できます。`; return output; } export async function viewBattleLog(saveKey: string): Promise<string> { const data = await storage.load(saveKey); if (!data.player.name) { return "プレイヤーが見つかりません。"; } if (!data.player.currentDungeon || data.player.currentDungeon.battleLog.length === 0) { return "表示できる戦闘ログがありません。"; } const battleLog = data.player.currentDungeon.battleLog; let output = `=== 戦闘ログ ===\n\n`; for (const battle of battleLog) { const icon = battle.victory ? '✅' : '❌'; output += `${icon} ${battle.floor}階: ${battle.enemyName}\n`; output += ` 与ダメージ: ${battle.damageDealt} / 被ダメージ: ${battle.damageTaken}\n`; output += ` クリティカル: ${battle.criticalHits}回 / 回避: ${battle.dodges}回\n`; if (battle.herbUsed) { output += ` 薬草使用: ✅ (HP回復)\n`; } if (battle.victory) { output += ` ゴールド: +${battle.goldEarned}\n`; if (battle.itemsDropped.length > 0) { output += ` ドロップ: ${battle.itemsDropped.map(i => i.name).join(', ')}\n`; } } output += '\n'; } return output; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bellsanct/mcp-dungeon-game'

If you have feedback or need assistance with the MCP directory API, please join our Discord server