Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Dice & Destiny RPG</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=MedievalSharp&display=swap'); | |
| body { | |
| font-family: 'MedievalSharp', cursive; | |
| background-image: url('https://images.unsplash.com/photo-1506318137071-a8e06380a6a1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80'); | |
| background-size: cover; | |
| background-attachment: fixed; | |
| color: #e2e8f0; | |
| } | |
| .stat-bar { | |
| height: 20px; | |
| background: linear-gradient(90deg, #4a5568 0%, #2d3748 50%, #4a5568 100%); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| .stat-fill { | |
| height: 100%; | |
| border-radius: 10px; | |
| transition: width 0.5s ease-in-out; | |
| } | |
| .hp-fill { background: linear-gradient(90deg, #dc2626 0%, #b91c1c 50%, #dc2626 100%); } | |
| .mana-fill { background: linear-gradient(90deg, #2563eb 0%, #1d4ed8 50%, #2563eb 100%); } | |
| .stamina-fill { background: linear-gradient(90deg, #16a34a 0%, #15803d 50%, #16a34a 100%); } | |
| .dice { | |
| animation: roll 0.5s ease-out; | |
| transform-style: preserve-3d; | |
| } | |
| @keyframes roll { | |
| 0% { transform: rotateX(0deg) rotateY(0deg); } | |
| 100% { transform: rotateX(720deg) rotateY(360deg); } | |
| } | |
| .character-card { | |
| transition: all 0.3s ease; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .character-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2); | |
| } | |
| .tooltip { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .tooltip .tooltiptext { | |
| visibility: hidden; | |
| width: 200px; | |
| background-color: #1e293b; | |
| color: #fff; | |
| text-align: center; | |
| border-radius: 6px; | |
| padding: 5px; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 125%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .tooltip:hover .tooltiptext { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| .ability-btn { | |
| transition: all 0.2s ease; | |
| } | |
| .ability-btn:hover { | |
| transform: scale(1.05); | |
| } | |
| .ability-btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| .dialog-container { | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(-20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen bg-gray-900 bg-opacity-90"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <h1 class="text-5xl font-bold text-yellow-400 mb-2">Dice & Destiny</h1> | |
| <p class="text-xl text-gray-300">A Tabletop RPG Experience in Your Browser</p> | |
| </header> | |
| <div id="game-container" class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Character Selection Section --> | |
| <div id="character-selection" class="lg:col-span-3 bg-gray-800 bg-opacity-75 rounded-lg p-6 shadow-xl"> | |
| <h2 class="text-3xl text-yellow-300 mb-6 text-center">Choose Your Hero</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> | |
| <!-- Warrior --> | |
| <div class="character-card bg-gray-700 p-4 rounded-lg cursor-pointer border-2 border-transparent hover:border-yellow-400" onclick="selectCharacter('warrior')"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="text-xl font-bold text-red-400">Warrior</h3> | |
| <i class="fas fa-shield-alt text-2xl text-red-300"></i> | |
| </div> | |
| <img src="https://i.imgur.com/JTQf8jD.png" alt="Warrior" class="w-full h-40 object-contain mb-3"> | |
| <div class="text-sm text-gray-300"> | |
| <p><span class="font-bold">HP:</span> High</p> | |
| <p><span class="font-bold">Damage:</span> Moderate</p> | |
| <p><span class="font-bold">Abilities:</span> Toughness, Powerful Strikes</p> | |
| <p class="mt-2 text-xs">A battle-hardened fighter with exceptional durability and strong melee attacks.</p> | |
| </div> | |
| </div> | |
| <!-- Rogue --> | |
| <div class="character-card bg-gray-700 p-4 rounded-lg cursor-pointer border-2 border-transparent hover:border-yellow-400" onclick="selectCharacter('rogue')"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="text-xl font-bold text-green-400">Rogue</h3> | |
| <i class="fas fa-mask text-2xl text-green-300"></i> | |
| </div> | |
| <img src="https://i.imgur.com/F4zVHj2.png" alt="Rogue" class="w-full h-40 object-contain mb-3"> | |
| <div class="text-sm text-gray-300"> | |
| <p><span class="font-bold">HP:</span> Low</p> | |
| <p><span class="font-bold">Damage:</span> High</p> | |
| <p><span class="font-bold">Abilities:</span> Sneak Attack, Evasion</p> | |
| <p class="mt-2 text-xs">A stealthy assassin who relies on speed and precision to land critical hits.</p> | |
| </div> | |
| </div> | |
| <!-- Mage --> | |
| <div class="character-card bg-gray-700 p-4 rounded-lg cursor-pointer border-2 border-transparent hover:border-yellow-400" onclick="selectCharacter('mage')"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="text-xl font-bold text-blue-400">Mage</h3> | |
| <i class="fas fa-hat-wizard text-2xl text-blue-300"></i> | |
| </div> | |
| <img src="https://i.imgur.com/9QZmXTK.png" alt="Mage" class="w-full h-40 object-contain mb-3"> | |
| <div class="text-sm text-gray-300"> | |
| <p><span class="font-bold">HP:</span> Very Low</p> | |
| <p><span class="font-bold">Damage:</span> Very High</p> | |
| <p><span class="font-bold">Abilities:</span> Fireball, Magic Shield</p> | |
| <p class="mt-2 text-xs">A master of arcane arts who can unleash devastating spells but is fragile.</p> | |
| </div> | |
| </div> | |
| <!-- Cleric --> | |
| <div class="character-card bg-gray-700 p-4 rounded-lg cursor-pointer border-2 border-transparent hover:border-yellow-400" onclick="selectCharacter('cleric')"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="text-xl font-bold text-purple-400">Cleric</h3> | |
| <i class="fas fa-bible text-2xl text-purple-300"></i> | |
| </div> | |
| <img src="https://i.imgur.com/lp8b5Ly.png" alt="Cleric" class="w-full h-40 object-contain mb-3"> | |
| <div class="text-sm text-gray-300"> | |
| <p><span class="font-bold">HP:</span> Moderate</p> | |
| <p><span class="font-bold">Damage:</span> Low</p> | |
| <p><span class="font-bold">Abilities:</span> Healing, Turn Undead</p> | |
| <p class="mt-2 text-xs">A holy warrior who can heal wounds and smite enemies with divine power.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="selected-character-info" class="mt-6 bg-gray-700 p-4 rounded-lg hidden"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <h3 id="selected-class" class="text-2xl font-bold"></h3> | |
| <p id="selected-description" class="text-gray-300"></p> | |
| </div> | |
| <button onclick="startGame()" class="bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-2 px-6 rounded-lg transition-colors flex items-center"> | |
| <i class="fas fa-dice mr-2"></i> Begin Adventure | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Game Area (Hidden Initially) --> | |
| <div id="game-area" class="lg:col-span-3 hidden"> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Player Stats --> | |
| <div class="bg-gray-800 bg-opacity-75 rounded-lg p-6 shadow-xl"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 id="player-name" class="text-2xl font-bold text-yellow-300"></h2> | |
| <span id="player-level" class="bg-yellow-600 text-black rounded-full px-3 py-1 text-sm font-bold">Level 1</span> | |
| </div> | |
| <div id="player-icon" class="flex justify-center mb-4"> | |
| <i class="fas fa-user-circle text-8xl"></i> | |
| </div> | |
| <div class="mb-4"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-red-300">HP</span> | |
| <span id="hp-text" class="font-bold">100/100</span> | |
| </div> | |
| <div class="stat-bar"> | |
| <div id="hp-fill" class="stat-fill hp-fill" style="width: 100%"></div> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-blue-300">Mana</span> | |
| <span id="mana-text" class="font-bold">50/50</span> | |
| </div> | |
| <div class="stat-bar"> | |
| <div id="mana-fill" class="stat-fill mana-fill" style="width: 100%"></div> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-green-300">Stamina</span> | |
| <span id="stamina-text" class="font-bold">80/80</span> | |
| </div> | |
| <div class="stat-bar"> | |
| <div id="stamina-fill" class="stat-fill stamina-fill" style="width: 100%"></div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 mb-4"> | |
| <div class="bg-gray-700 p-2 rounded text-center"> | |
| <div class="text-xl font-bold text-yellow-300">10</div> | |
| <div class="text-xs text-gray-300">Strength</div> | |
| </div> | |
| <div class="bg-gray-700 p-2 rounded text-center"> | |
| <div class="text-xl font-bold text-yellow-300">12</div> | |
| <div class="text-xs text-gray-300">Dexterity</div> | |
| </div> | |
| <div class="bg-gray-700 p-2 rounded text-center"> | |
| <div class="text-xl font-bold text-yellow-300">14</div> | |
| <div class="text-xs text-gray-300">Intellect</div> | |
| </div> | |
| <div class="bg-gray-700 p-2 rounded text-center"> | |
| <div class="text-xl font-bold text-yellow-300">8</div> | |
| <div class="text-xs text-gray-300">Constitution</div> | |
| </div> | |
| </div> | |
| <div class="mt-4"> | |
| <h3 class="text-lg font-bold text-yellow-300 mb-2">Equipment</h3> | |
| <div class="grid grid-cols-3 gap-2 text-center"> | |
| <div class="tooltip"> | |
| <div class="bg-gray-700 p-2 rounded cursor-pointer"> | |
| <i class="fas fa-sword text-xl"></i> | |
| </div> | |
| <span class="tooltiptext">Iron Sword - 1d8 damage</span> | |
| </div> | |
| <div class="tooltip"> | |
| <div class="bg-gray-700 p-2 rounded cursor-pointer"> | |
| <i class="fas fa-tshirt text-xl"></i> | |
| </div> | |
| <span class="tooltiptext">Leather Armor - +2 AC</span> | |
| </div> | |
| <div class="tooltip"> | |
| <div class="bg-gray-700 p-2 rounded cursor-pointer"> | |
| <i class="fas fa-ring text-xl"></i> | |
| </div> | |
| <span class="tooltiptext">Ring of Vitality - +5 HP</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Game Log --> | |
| <div class="bg-gray-800 bg-opacity-75 rounded-lg p-6 shadow-xl lg:col-span-2"> | |
| <div class="h-96 overflow-y-auto mb-4 bg-black bg-opacity-50 p-3 rounded-lg border border-gray-700" id="game-log"> | |
| <div class="text-yellow-300 text-center italic">Welcome to Dice & Destiny! Your adventure begins...</div> | |
| </div> | |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-2 mb-4" id="action-buttons"> | |
| <!-- Buttons will be added dynamically --> | |
| </div> | |
| <div class="flex flex-wrap gap-2 justify-between"> | |
| <button onclick="rollDice('d4')" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice-d4 mr-2"></i> d4 | |
| </button> | |
| <button onclick="rollDice('d6')" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice-d6 mr-2"></i> d6 | |
| </button> | |
| <button onclick="rollDice('d8')" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice-d8 mr-2"></i> d8 | |
| </button> | |
| <button onclick="rollDice('d10')" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice-d10 mr-2"></i> d10 | |
| </button> | |
| <button onclick="rollDice('d12')" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice-d12 mr-2"></i> d12 | |
| </button> | |
| <button onclick="rollDice('d20')" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice-d20 mr-2"></i> d20 | |
| </button> | |
| <button onclick="rollDice('d100')" class="bg-pink-600 hover:bg-pink-700 text-white font-bold py-2 px-4 rounded flex items-center"> | |
| <i class="fas fa-dice mr-2"></i> d100 | |
| </button> | |
| </div> | |
| <div class="mt-4 flex items-center"> | |
| <div id="dice-result-container" class="mr-4 hidden"> | |
| <div class="text-xl font-bold text-yellow-300">Rolled: <span id="dice-result" class="text-white">0</span></div> | |
| <div class="text-xs text-gray-400">Dice: <span id="dice-type" class="text-white">d20</span></div> | |
| </div> | |
| <div id="dice-animation" class="dice text-4xl hidden"> | |
| <i class="fas fa-dice-d20"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Enemy Encounter --> | |
| <div id="enemy-container" class="lg:col-span-3 bg-gray-800 bg-opacity-75 rounded-lg p-6 shadow-xl hidden"> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="md:col-span-2"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 id="enemy-name" class="text-2xl font-bold text-red-400"></h2> | |
| <span id="enemy-health" class="font-bold">100/100 HP</span> | |
| </div> | |
| <div class="mb-4"> | |
| <div class="stat-bar"> | |
| <div id="enemy-hp-fill" class="stat-fill hp-fill" style="width: 100%"></div> | |
| </div> | |
| </div> | |
| <div id="enemy-image" class="flex justify-center mb-4"> | |
| <img src="https://i.imgur.com/gmQdQ9T.png" alt="Goblin" class="h-48"> | |
| </div> | |
| <div class="grid grid-cols-3 gap-4 text-center"> | |
| <div class="bg-gray-700 p-2 rounded"> | |
| <div class="text-lg font-bold">5</div> | |
| <div class="text-xs">ATK</div> | |
| </div> | |
| <div class="bg-gray-700 p-2 rounded"> | |
| <div class="text-lg font-bold">12</div> | |
| <div class="text-xs">AC</div> | |
| </div> | |
| <div class="bg-gray-700 p-2 rounded"> | |
| <div class="text-lg font-bold">50</div> | |
| <div class="text-xs">XP</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-bold text-yellow-300 mb-2">Combat Actions</h3> | |
| <div class="space-y-2"> | |
| <button onclick="attackEnemy()" class="ability-btn bg-red-600 hover:bg-red-700 text-white w-full py-2 px-4 rounded flex items-center justify-between"> | |
| <span>Attack</span> | |
| <span class="text-xs">1d20 + STR</span> | |
| </button> | |
| <button onclick="castSpell('fireball')" class="ability-btn bg-orange-500 hover:bg-orange-600 text-white w-full py-2 px-4 rounded flex items-center justify-between"> | |
| <span>Fireball</span> | |
| <span class="text-xs">3d6 damage, 10 mana</span> | |
| </button> | |
| <button onclick="useAbility('heal')" class="ability-btn bg-green-600 hover:bg-green-700 text-white w-full py-2 px-4 rounded flex items-center justify-between"> | |
| <span>Heal</span> | |
| <span class="text-xs">2d8 + WIS, 15 mana</span> | |
| </button> | |
| <button onclick="useAbility('dodge')" class="ability-btn bg-blue-600 hover:bg-blue-700 text-white w-full py-2 px-4 rounded flex items-center justify-between"> | |
| <span>Dodge</span> | |
| <span class="text-xs">+2 AC, 5 stamina</span> | |
| </button> | |
| </div> | |
| <div class="mt-4 bg-gray-700 p-3 rounded"> | |
| <h4 class="text-md font-bold mb-1">Enemy Description</h4> | |
| <p id="enemy-desc" class="text-sm text-gray-300">A small but vicious goblin with sharp teeth and a rusty dagger. It looks hungry for trouble.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Inventory & Quests --> | |
| <div id="inventory-quests" class="lg:col-span-3 grid grid-cols-1 lg:grid-cols-2 gap-6 hidden"> | |
| <!-- Inventory --> | |
| <div class="bg-gray-800 bg-opacity-75 rounded-lg p-6 shadow-xl"> | |
| <h2 class="text-2xl font-bold text-yellow-300 mb-4">Inventory</h2> | |
| <div class="grid grid-cols-3 gap-2"> | |
| <div class="bg-gray-700 p-3 rounded cursor-pointer hover:bg-gray-600 transition-colors"> | |
| <div class="flex justify-between items-start"> | |
| <i class="fas fa-flask text-xl text-blue-300"></i> | |
| <span class="bg-yellow-600 text-black text-xs px-1 rounded">5</span> | |
| </div> | |
| <div class="text-sm mt-1">Health Potion</div> | |
| </div> | |
| <div class="bg-gray-700 p-3 rounded cursor-pointer hover:bg-gray-600 transition-colors"> | |
| <i class="fas fa-scroll text-xl text-yellow-300"></i> | |
| <div class="text-sm mt-1">Scroll of Fire</div> | |
| </div> | |
| <div class="bg-gray-700 p-3 rounded cursor-pointer hover:bg-gray-600 transition-colors"> | |
| <i class="fas fa-key text-xl text-yellow-400"></i> | |
| <div class="text-sm mt-1">Old Key</div> | |
| </div> | |
| <div class="bg-gray-700 p-3 rounded cursor-pointer hover:bg-gray-600 transition-colors"> | |
| <i class="fas fa-bread-slice text-xl text-yellow-200"></i> | |
| <div class="text-sm mt-1">Rations (3)</div> | |
| </div> | |
| <div class="bg-gray-700 p-3 rounded cursor-pointer hover:bg-gray-600 transition-colors"> | |
| <i class="fas fa-gem text-xl text-purple-300"></i> | |
| <div class="text-sm mt-1">Amethyst</div> | |
| </div> | |
| <div class="bg-gray-700 p-3 rounded cursor-pointer hover:bg-gray-600 transition-colors"> | |
| <i class="fas fa-coins text-xl text-yellow-500"></i> | |
| <div class="text-sm mt-1">25 Gold</div> | |
| </div> | |
| </div> | |
| <div class="mt-4"> | |
| <button onclick="useItem('Health Potion')" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"> | |
| Use Health Potion | |
| </button> | |
| <button onclick="sellItems()" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded ml-2"> | |
| Sell Items | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Quests --> | |
| <div class="bg-gray-800 bg-opacity-75 rounded-lg p-6 shadow-xl"> | |
| <h2 class="text-2xl font-bold text-yellow-300 mb-4">Quests</h2> | |
| <div class="mb-4 bg-gray-700 p-4 rounded border-l-4 border-yellow-500"> | |
| <h3 class="text-lg font-bold flex items-center"> | |
| <i class="fas fa-check-circle text-green-400 mr-2"></i> | |
| The Missing Blacksmith | |
| </h3> | |
| <p class="text-sm mt-1 text-gray-300">Find the blacksmith's lost tools in the goblin caves. Reward: 50 gold</p> | |
| <div class="w-full bg-gray-600 rounded-full h-2 mt-2"> | |
| <div class="bg-green-400 h-2 rounded-full" style="width: 100%"></div> | |
| </div> | |
| <p class="text-xs text-right mt-1 text-gray-400">Completed</p> | |
| </div> | |
| <div class="mb-4 bg-gray-700 p-4 rounded border-l-4 border-yellow-500"> | |
| <h3 class="text-lg font-bold flex items-center"> | |
| <i class="fas fa-hourglass-half text-yellow-400 mr-2"></i> | |
| Bandit Camp Infiltration | |
| </h3> | |
| <p class="text-sm mt-1 text-gray-300">Gather information about the bandit camp near the river. Reward: Leather Armor</p> | |
| <div class="w-full bg-gray-600 rounded-full h-2 mt-2"> | |
| <div class="bg-blue-400 h-2 rounded-full" style="width: 65%"></div> | |
| </div> | |
| <p class="text-xs text-right mt-1 text-gray-400">65% complete</p> | |
| </div> | |
| <div class="mb-4 bg-gray-700 p-4 rounded border-l-4 border-blue-500"> | |
| <h3 class="text-lg font-bold flex items-center"> | |
| <i class="fas fa-exclamation-circle text-blue-400 mr-2"></i> | |
| Ancient Relics | |
| </h3> | |
| <p class="text-sm mt-1 text-gray-300">Recover the three ancient relics from the ruins for the mage's guild. Reward: Arcane Tome</p> | |
| <div class="flex space-x-2 mt-2"> | |
| <div class="w-full bg-gray-600 rounded-full h-2"> | |
| <div class="bg-blue-400 h-2 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <div class="w-full bg-gray-600 rounded-full h-2"> | |
| <div class="bg-blue-400 h-2 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <div class="w-full bg-gray-600 rounded-full h-2"> | |
| <div class="bg-blue-400 h-2 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <p class="text-xs text-right mt-1 text-gray-400">0/3 relics found</p> | |
| </div> | |
| <button onclick="showQuestDetails()" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full"> | |
| View Quest Details | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Game Over Modal --> | |
| <div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center hidden z-50"> | |
| <div class="bg-gray-800 p-8 rounded-lg max-w-md w-full dialog-container"> | |
| <div class="text-center"> | |
| <h2 class="text-3xl font-bold text-red-500 mb-4">Game Over!</h2> | |
| <p class="text-gray-300 mb-6">Your brave adventurer has fallen in battle. But every end is a new beginning...</p> | |
| <div class="mb-6"> | |
| <p class="text-yellow-300">Your accomplishments:</p> | |
| <ul class="text-gray-300 text-left list-disc pl-5 mt-2"> | |
| <li>Defeated 3 enemies</li> | |
| <li>Completed 1 quest</li> | |
| <li>Reached level 2</li> | |
| </ul> | |
| </div> | |
| <div class="flex justify-center space-x-4"> | |
| <button onclick="retryGame()" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-6 rounded"> | |
| Try Again | |
| </button> | |
| <button onclick="newCharacter()" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-6 rounded"> | |
| New Hero | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Level Up Modal --> | |
| <div id="level-up-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center hidden z-50"> | |
| <div class="bg-gray-800 p-8 rounded-lg max-w-md w-full dialog-container"> | |
| <div class="text-center"> | |
| <h2 class="text-3xl font-bold text-yellow-400 mb-4">Level Up!</h2> | |
| <p class="text-gray-300 mb-6">Congratulations! You've reached level <span id="new-level" class="text-yellow-300">2</span>.</p> | |
| <div class="mb-6"> | |
| <p class="text-yellow-300 mb-3">Choose an attribute to improve:</p> | |
| <div class="grid grid-cols-3 gap-2"> | |
| <button onclick="levelUpAttribute('strength')" class="bg-red-700 hover:bg-red-800 text-white font-bold py-2 px-4 rounded"> | |
| Strength +1 | |
| </button> | |
| <button onclick="levelUpAttribute('dexterity')" class="bg-green-700 hover:bg-green-800 text-white font-bold py-2 px-4 rounded"> | |
| Dexterity +1 | |
| </button> | |
| <button onclick="levelUpAttribute('intellect')" class="bg-blue-700 hover:bg-blue-800 text-white font-bold py-2 px-4 rounded"> | |
| Intellect +1 | |
| </button> | |
| </div> | |
| </div> | |
| <p class="text-gray-300 text-sm">HP and resource pools have been fully restored.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game State | |
| const gameState = { | |
| character: null, | |
| hp: 0, | |
| maxHp: 0, | |
| mana: 0, | |
| maxMana: 0, | |
| stamina: 0, | |
| maxStamina: 0, | |
| level: 1, | |
| xp: 0, | |
| xpToNextLevel: 100, | |
| enemiesDefeated: 0, | |
| currentEnemy: null, | |
| inCombat: false, | |
| abilities: [], | |
| inventory: [], | |
| quests: [] | |
| }; | |
| // Character Classes | |
| const characterClasses = { | |
| warrior: { | |
| name: "Warrior", | |
| icon: "fa-shield-alt", | |
| color: "red", | |
| description: "A battle-hardened fighter with high health and strong physical attacks.", | |
| stats: { | |
| str: 16, | |
| dex: 12, | |
| int: 8, | |
| con: 14 | |
| }, | |
| resources: { | |
| hp: 120, | |
| mana: 20, | |
| stamina: 100 | |
| }, | |
| abilities: [ | |
| { | |
| name: "Power Strike", | |
| description: "A mighty swing that deals extra damage.", | |
| cost: 15, | |
| costType: "stamina", | |
| effect: "2d8 + STR damage" | |
| }, | |
| { | |
| name: "Defensive Stance", | |
| description: "Brace yourself, reducing damage taken by 50% for one turn.", | |
| cost: 20, | |
| costType: "stamina", | |
| effect: "Damage reduction" | |
| } | |
| ] | |
| }, | |
| rogue: { | |
| name: "Rogue", | |
| icon: "fa-mask", | |
| color: "green", | |
| description: "A stealthy assassin who relies on speed and precision to land critical hits.", | |
| stats: { | |
| str: 10, | |
| dex: 18, | |
| int: 12, | |
| con: 10 | |
| }, | |
| resources: { | |
| hp: 80, | |
| mana: 30, | |
| stamina: 120 | |
| }, | |
| abilities: [ | |
| { | |
| name: "Backstab", | |
| description: "Strike an unsuspecting foe for critical damage.", | |
| cost: 20, | |
| costType: "stamina", | |
| effect: "3d6 + DEX damage (2x on surprise)" | |
| }, | |
| { | |
| name: "Evasion", | |
| description: "Dodge the next attack automatically.", | |
| cost: 25, | |
| costType: "stamina", | |
| effect: "Avoid next attack" | |
| } | |
| ] | |
| }, | |
| mage: { | |
| name: "Mage", | |
| icon: "fa-hat-wizard", | |
| color: "blue", | |
| description: "A master of arcane arts who can unleash devastating spells but is fragile.", | |
| stats: { | |
| str: 8, | |
| dex: 10, | |
| int: 18, | |
| con: 8 | |
| }, | |
| resources: { | |
| hp: 60, | |
| mana: 150, | |
| stamina: 40 | |
| }, | |
| abilities: [ | |
| { | |
| name: "Fireball", | |
| description: "Launch a fiery projectile that explodes on impact.", | |
| cost: 30, | |
| costType: "mana", | |
| effect: "3d6 + INT damage in area" | |
| }, | |
| { | |
| name: "Magic Shield", | |
| description: "Create a protective barrier that absorbs damage.", | |
| cost: 25, | |
| costType: "mana", | |
| effect: "Absorb 15 damage" | |
| } | |
| ] | |
| }, | |
| cleric: { | |
| name: "Cleric", | |
| icon: "fa-bible", | |
| color: "purple", | |
| description: "A holy warrior who can heal wounds and smite enemies with divine power.", | |
| stats: { | |
| str: 12, | |
| dex: 10, | |
| int: 16, | |
| con: 12 | |
| }, | |
| resources: { | |
| hp: 100, | |
| mana: 100, | |
| stamina: 60 | |
| }, | |
| abilities: [ | |
| { | |
| name: "Healing Light", | |
| description: "Channel divine energy to restore health.", | |
| cost: 20, | |
| costType: "mana", | |
| effect: "2d8 + WIS healing" | |
| }, | |
| { | |
| name: "Smite", | |
| description: "Infuse your attack with divine energy.", | |
| cost: 25, | |
| costType: "mana", | |
| effect: "1d8 + STR + 1d8 radiant damage" | |
| } | |
| ] | |
| } | |
| }; | |
| // Enemies | |
| const enemies = [ | |
| { | |
| name: "Goblin", | |
| image: "https://i.imgur.com/gmQdQ9T.png", | |
| maxHp: 40, | |
| hp: 40, | |
| attack: 5, | |
| ac: 12, | |
| xp: 50, | |
| description: "A small but vicious goblin with sharp teeth and a rusty dagger. It looks hungry for trouble." | |
| }, | |
| { | |
| name: "Orc", | |
| image: "https://i.imgur.com/QyRmS0c.png", | |
| maxHp: 65, | |
| hp: 65, | |
| attack: 8, | |
| ac: 14, | |
| xp: 100, | |
| description: "A hulking orc warrior with crude armor and a massive club. His eyes burn with battle lust." | |
| }, | |
| { | |
| name: "Skeleton", | |
| image: "https://i.imgur.com/CBYlZ7t.png", | |
| maxHp: 30, | |
| hp: 30, | |
| attack: 4, | |
| ac: 13, | |
| xp: 40, | |
| description: "An animated skeleton with a rusted sword. Its hollow eye sockets glow with an eerie red light." | |
| }, | |
| { | |
| name: "Bandit", | |
| image: "https://i.imgur.com/wxY8y1Q.png", | |
| maxHp: 50, | |
| hp: 50, | |
| attack: 6, | |
| ac: 13, | |
| xp: 75, | |
| description: "A human bandit with leather armor and a short sword. He looks desperate but dangerous." | |
| } | |
| ]; | |
| // Quests | |
| const quests = [ | |
| { | |
| name: "The Missing Blacksmith", | |
| description: "Find the blacksmith's lost tools in the goblin caves.", | |
| reward: "50 gold", | |
| progress: 1, | |
| completed: true | |
| }, | |
| { | |
| name: "Bandit Camp Infiltration", | |
| description: "Gather information about the bandit camp near the river.", | |
| reward: "Leather Armor", | |
| progress: 0.65, | |
| completed: false | |
| }, | |
| { | |
| name: "Ancient Relics", | |
| description: "Recover the three ancient relics from the ruins for the mage's guild.", | |
| reward: "Arcane Tome", | |
| progress: 0, | |
| completed: false | |
| } | |
| ]; | |
| // Inventory | |
| const inventoryItems = [ | |
| { | |
| name: "Health Potion", | |
| icon: "fa-flask", | |
| color: "blue-300", | |
| quantity: 5, | |
| description: "Restores 2d4+2 HP when consumed." | |
| }, | |
| { | |
| name: "Scroll of Fire", | |
| icon: "fa-scroll", | |
| color: "yellow-300", | |
| quantity: 1, | |
| description: "Can cast Fireball once (3d6 fire damage)." | |
| }, | |
| { | |
| name: "Old Key", | |
| icon: "fa-key", | |
| color: "yellow-400", | |
| quantity: 1, | |
| description: "A rusty old key. It might open something important." | |
| }, | |
| { | |
| name: "Rations", | |
| icon: "fa-bread-slice", | |
| color: "yellow-200", | |
| quantity: 3, | |
| description: "Enough food for one day of travel." | |
| }, | |
| { | |
| name: "Amethyst", | |
| icon: "fa-gem", | |
| color: "purple-300", | |
| quantity: 1, | |
| description: "A precious gemstone worth about 25 gold." | |
| }, | |
| { | |
| name: "Gold", | |
| icon: "fa-coins", | |
| color: "yellow-500", | |
| quantity: 25, | |
| description: "Shiny gold coins for purchasing items." | |
| } | |
| ]; | |
| // DOM Elements | |
| const characterSelection = document.getElementById('character-selection'); | |
| const selectedCharacterInfo = document.getElementById('selected-character-info'); | |
| const selectedClass = document.getElementById('selected-class'); | |
| const selectedDescription = document.getElementById('selected-description'); | |
| const gameArea = document.getElementById('game-area'); | |
| const gameContainer = document.getElementById('game-container'); | |
| const playerName = document.getElementById('player-name'); | |
| const playerLevel = document.getElementById('player-level'); | |
| const playerIcon = document.getElementById('player-icon'); | |
| const hpText = document.getElementById('hp-text'); | |
| const hpFill = document.getElementById('hp-fill'); | |
| const manaText = document.getElementById('mana-text'); | |
| const manaFill = document.getElementById('mana-fill'); | |
| const staminaText = document.getElementById('stamina-text'); | |
| const staminaFill = document.getElementById('stamina-fill'); | |
| const actionButtons = document.getElementById('action-buttons'); | |
| const gameLog = document.getElementById('game-log'); | |
| const enemyContainer = document.getElementById('enemy-container'); | |
| const enemyName = document.getElementById('enemy-name'); | |
| const enemyHealth = document.getElementById('enemy-health'); | |
| const enemyHpFill = document.getElementById('enemy-hp-fill'); | |
| const enemyImage = document.getElementById('enemy-image'); | |
| const enemyDesc = document.getElementById('enemy-desc'); | |
| const inventoryQuests = document.getElementById('inventory-quests'); | |
| const diceResultContainer = document.getElementById('dice-result-container'); | |
| const diceResult = document.getElementById('dice-result'); | |
| const diceType = document.getElementById('dice-type'); | |
| const diceAnimation = document.getElementById('dice-animation'); | |
| const gameOverModal = document.getElementById('game-over-modal'); | |
| const levelUpModal = document.getElementById('level-up-modal'); | |
| const newLevel = document.getElementById('new-level'); | |
| // Game Functions | |
| function selectCharacter(characterClass) { | |
| const character = characterClasses[characterClass]; | |
| gameState.character = characterClass; | |
| selectedCharacterInfo.classList.remove('hidden'); | |
| selectedClass.textContent = character.name; | |
| selectedClass.className = `text-2xl font-bold text-${character.color}-400`; | |
| selectedDescription.textContent = character.description; | |
| // Highlight selected character card | |
| document.querySelectorAll('.character-card').forEach(card => { | |
| card.classList.remove('border-yellow-400', 'bg-gray-600'); | |
| }); | |
| const selectedCard = document.querySelector(`[onclick="selectCharacter('${characterClass}')"]`); | |
| selectedCard.classList.add('border-yellow-400', 'bg-gray-600'); | |
| addToLog(`Selected character: ${character.name}`); | |
| } | |
| function startGame() { | |
| if (!gameState.character) { | |
| alert("Please select a character first!"); | |
| return; | |
| } | |
| const character = characterClasses[gameState.character]; | |
| // Set up player stats | |
| gameState.hp = character.resources.hp; | |
| gameState.maxHp = character.resources.hp; | |
| gameState.mana = character.resources.mana; | |
| gameState.maxMana = character.resources.mana; | |
| gameState.stamina = character.resources.stamina; | |
| gameState.maxStamina = character.resources.stamina; | |
| gameState.level = 1; | |
| gameState.xp = 0; | |
| gameState.xpToNextLevel = 100; | |
| gameState.enemiesDefeated = 0; | |
| gameState.inventory = JSON.parse(JSON.stringify(inventoryItems)); | |
| gameState.quests = JSON.parse(JSON.stringify(quests)); | |
| gameState.abilities = JSON.parse(JSON.stringify(character.abilities)); | |
| // Update UI | |
| characterSelection.classList.add('hidden'); | |
| gameArea.classList.remove('hidden'); | |
| playerName.textContent = character.name; | |
| playerName.className = `text-2xl font-bold text-${character.color}-300`; | |
| playerLevel.textContent = `Level ${gameState.level}`; | |
| playerIcon.innerHTML = `<i class="fas ${character.icon} text-8xl text-${character.color}-300"></i>`; | |
| updateStats(); | |
| // Set up action buttons | |
| setupActionButtons(); | |
| // Add initial game log messages | |
| addToLog(`Welcome, brave ${character.name}! Your adventure begins now.`, 'yellow'); | |
| addToLog("You find yourself in a small village at the edge of a dark forest."); | |
| addToLog("The villagers speak of increasing monster attacks in the area."); | |
| addToLog("You decide to help by exploring the forest and eliminating threats."); | |
| // Show first quest | |
| setTimeout(() => { | |
| addToLog(`Quest: ${gameState.quests[0].name} - ${gameState.quests[0].description}`, 'yellow'); | |
| addToLog("Reward: " + gameState.quests[0].reward, 'yellow'); | |
| addToLog("You set out towards the goblin caves to the east..."); | |
| // Trigger first encounter after a delay | |
| setTimeout(() => { | |
| createRandomEncounter(); | |
| }, 2000); | |
| }, 1000); | |
| } | |
| function updateStats() { | |
| const character = characterClasses[gameState.character]; | |
| hpText.textContent = `${gameState.hp}/${gameState.maxHp}`; | |
| manaText.textContent = `${gameState.mana}/${gameState.maxMana}`; | |
| staminaText.textContent = `${gameState.stamina}/${gameState.maxStamina}`; | |
| hpFill.style.width = `${(gameState.hp / gameState.maxHp) * 100}%`; | |
| manaFill.style.width = `${(gameState.mana / gameState.maxMana) * 100}%`; | |
| staminaFill.style.width = `${(gameState.stamina / gameState.maxStamina) * 100}%`; | |
| playerLevel.textContent = `Level ${gameState.level}`; | |
| } | |
| function setupActionButtons() { | |
| actionButtons.innerHTML = ''; | |
| // Common actions | |
| const commonActions = [ | |
| { name: "Explore", icon: "fa-binoculars", color: "green", action: "explore" }, | |
| { name: "Rest", icon: "fa-campground", color: "blue", action: "rest" }, | |
| { name: "Inventory", icon: "fa-backpack", color: "yellow", action: "showInventory" }, | |
| { name: "Quests", icon: "fa-scroll", color: "purple", action: "showQuests" } | |
| ]; | |
| commonActions.forEach(action => { | |
| const button = document.createElement('button'); | |
| button.className = `bg-${action.color}-600 hover:bg-${action.color}-700 text-white font-bold py-2 px-4 rounded flex items-center justify-center`; | |
| button.innerHTML = `<i class="fas ${action.icon} mr-2"></i> ${action.name}`; | |
| button.onclick = function() { handleAction(action.action); }; | |
| actionButtons.appendChild(button); | |
| }); | |
| // Class-specific abilities | |
| gameState.abilities.forEach(ability => { | |
| const button = document.createElement('button'); | |
| button.className = `ability-btn bg-${gameState.character === 'mage' ? 'purple' : gameState.character === 'cleric' ? 'blue' : 'gray'}-600 hover:bg-${gameState.character === 'mage' ? 'purple' : gameState.character === 'cleric' ? 'blue' : 'gray'}-700 text-white font-bold py-2 px-4 rounded col-span-2 md:col-span-1`; | |
| button.innerHTML = ability.name; | |
| button.onclick = function() { useAbility(ability.name.toLowerCase().replace(' ', '-')); }; | |
| actionButtons.appendChild(button); | |
| }); | |
| } | |
| function handleAction(action) { | |
| switch(action) { | |
| case 'explore': | |
| if (!gameState.inCombat) { | |
| addToLog("You venture forth, looking for adventure..."); | |
| setTimeout(() => { | |
| createRandomEncounter(); | |
| }, 1500); | |
| } else { | |
| addToLog("You can't explore while in combat!", 'red'); | |
| } | |
| break; | |
| case 'rest': | |
| if (!gameState.inCombat) { | |
| const hpRecovered = Math.min(Math.floor(gameState.maxHp * 0.3), gameState.maxHp - gameState.hp); | |
| const manaRecovered = Math.min(Math.floor(gameState.maxMana * 0.5), gameState.maxMana - gameState.mana); | |
| const staminaRecovered = Math.min(Math.floor(gameState.maxStamina * 0.7), gameState.maxStamina - gameState.stamina); | |
| gameState.hp += hpRecovered; | |
| gameState.mana += manaRecovered; | |
| gameState.stamina += staminaRecovered; | |
| addToLog(`You take a moment to rest and recover.`, 'blue'); | |
| if (hpRecovered > 0) addToLog(`+${hpRecovered} HP`); | |
| if (manaRecovered > 0) addToLog(`+${manaRecovered} Mana`); | |
| if (staminaRecovered > 0) addToLog(`+${staminaRecovered} Stamina`); | |
| updateStats(); | |
| } else { | |
| addToLog("You can't rest in the middle of combat!", 'red'); | |
| } | |
| break; | |
| case 'showInventory': | |
| inventoryQuests.classList.remove('hidden'); | |
| enemyContainer.classList.add('hidden'); | |
| break; | |
| case 'showQuests': | |
| inventoryQuests.classList.remove('hidden'); | |
| enemyContainer.classList.add('hidden'); | |
| break; | |
| } | |
| } | |
| function createRandomEncounter() { | |
| if (gameState.inCombat) return; | |
| const randomEnemyIndex = Math.floor(Math.random() * enemies.length); | |
| gameState.currentEnemy = JSON.parse(JSON.stringify(enemies[randomEnemyIndex])); | |
| gameState.inCombat = true; | |
| enemyContainer.classList.remove('hidden'); | |
| inventoryQuests.classList.add('hidden'); | |
| enemyName.textContent = gameState.currentEnemy.name; | |
| enemyHealth.textContent = `${gameState.currentEnemy.hp}/${gameState.currentEnemy.maxHp} HP`; | |
| enemyHpFill.style.width = '100%'; | |
| enemyImage.innerHTML = `<img src="${gameState.currentEnemy.image}" alt="${gameState.currentEnemy.name}" class="h-48">`; | |
| enemyDesc.textContent = gameState.currentEnemy.description; | |
| addToLog(`You encounter a ${gameState.currentEnemy.name}!`, 'red'); | |
| } | |
| function attackEnemy() { | |
| if (!gameState.inCombat || !gameState.currentEnemy) return; | |
| // Player attack | |
| const attackRoll = rollDiceInBackground('d20'); | |
| const attackBonus = Math.floor(characterClasses[gameState.character].stats.str / 4); | |
| setTimeout(() => { | |
| if (attackRoll + attackBonus >= gameState.currentEnemy.ac) { | |
| const damageRoll = rollDiceInBackground('d8'); | |
| setTimeout(() => { | |
| const damage = damageRoll; | |
| gameState.currentEnemy.hp -= damage; | |
| if (gameState.currentEnemy.hp < 0) gameState.currentEnemy.hp = 0; | |
| enemyHealth.textContent = `${gameState.currentEnemy.hp}/${gameState.currentEnemy.maxHp} HP`; | |
| enemyHpFill.style.width = `${(gameState.currentEnemy.hp / gameState.currentEnemy.maxHp) * 100}%`; | |
| addToLog(`You hit the ${gameState.currentEnemy.name} for ${damage} damage!`, 'yellow'); | |
| if (gameState.currentEnemy.hp <= 0) { | |
| enemyDefeated(); | |
| return; | |
| } | |
| // Enemy attack | |
| setTimeout(() => { | |
| enemyAttack(); | |
| }, 1000); | |
| }, 500); | |
| } else { | |
| addToLog(`Your attack misses the ${gameState.currentEnemy.name}!`, 'gray'); | |
| // Enemy attack | |
| setTimeout(() => { | |
| enemyAttack(); | |
| }, 1000); | |
| } | |
| }, 500); | |
| } | |
| function enemyAttack() { | |
| if (!gameState.inCombat || !gameState.currentEnemy || gameState.currentEnemy.hp <= 0) return; | |
| const attackRoll = rollDiceInBackground('d20'); | |
| setTimeout(() => { | |
| if (attackRoll >= 10) { // Simple AC calculation | |
| const damageRoll = rollDiceInBackground('d6'); | |
| setTimeout(() => { | |
| const damage = damageRoll; | |
| gameState.hp -= damage; | |
| if (gameState.hp < 0) gameState.hp = 0; | |
| updateStats(); | |
| addToLog(`The ${gameState.currentEnemy.name} hits you for ${damage} damage!`, 'red'); | |
| if (gameState.hp <= 0) { | |
| gameOver(); | |
| } | |
| }, 500); | |
| } else { | |
| addToLog(`The ${gameState.currentEnemy.name}'s attack misses you!`, 'gray'); | |
| } | |
| }, 500); | |
| } | |
| function enemyDefeated() { | |
| addToLog(`You defeated the ${gameState.currentEnemy.name}!`, 'green'); | |
| addToLog(`+${gameState.currentEnemy.xp} XP gained`, 'yellow'); | |
| gameState.xp += gameState.currentEnemy.xp; | |
| gameState.enemiesDefeated++; | |
| // Check for level up | |
| if (gameState.xp >= gameState.xpToNextLevel) { | |
| levelUp(); | |
| } | |
| // Loot drop | |
| const lootChance = Math.random(); | |
| if (lootChance > 0.7) { | |
| const goldDrop = Math.floor(Math.random() * 20) + 10; | |
| addToLog(`You found ${goldDrop} gold coins on the enemy!`, 'yellow'); | |
| const goldIndex = gameState.inventory.findIndex(item => item.name === "Gold"); | |
| if (goldIndex !== -1) { | |
| gameState.inventory[goldIndex].quantity += goldDrop; | |
| } | |
| } | |
| gameState.inCombat = false; | |
| gameState.currentEnemy = null; | |
| setTimeout(() => { | |
| enemyContainer.classList.add('hidden'); | |
| // Auto progress quest if enemy was goblin | |
| if (gameState.enemiesDefeated >= 3) { | |
| gameState.quests[0].progress = 1; | |
| gameState.quests[0].completed = true; | |
| addToLog("Quest Complete: The Missing Blacksmith", 'yellow'); | |
| // Add reward | |
| const goldIndex = gameState.inventory.findIndex(item => item.name === "Gold"); | |
| if (goldIndex !== -1) { | |
| gameState.inventory[goldIndex].quantity += 50; | |
| } | |
| addToLog("You received 50 gold as a reward!", 'yellow'); | |
| addToLog("You complete the quest and return to the village."); | |
| // Start next quest | |
| setTimeout(() => { | |
| addToLog(`New Quest: ${gameState.quests[1].name} - ${gameState.quests[1].description}`); | |
| addToLog("Reward: " + gameState.quests[1].reward); | |
| }, 1500); | |
| } | |
| }, 1500); | |
| } | |
| function useAbility(ability) { | |
| if (!gameState.inCombat || !gameState.currentEnemy) { | |
| addToLog("You can only use abilities in combat!", 'red'); | |
| return; | |
| } | |
| // Find the ability | |
| const foundAbility = gameState.abilities.find(a => a.name.toLowerCase().replace(' ', '-') === ability); | |
| if (!foundAbility) { | |
| addToLog("Ability not found!", 'red'); | |
| return; | |
| } | |
| // Check resource | |
| if (foundAbility.costType === 'mana' && gameState.mana < foundAbility.cost) { | |
| addToLog(`Not enough mana to use ${foundAbility.name}!`, 'red'); | |
| return; | |
| } else if (foundAbility.costType === 'stamina' && gameState.stamina < foundAbility.cost) { | |
| addToLog(`Not enough stamina to use ${foundAbility.name}!`, 'red'); | |
| return; | |
| } | |
| // Deduct resource | |
| if (foundAbility.costType === 'mana') { | |
| gameState.mana -= foundAbility.cost; | |
| } else { | |
| gameState.stamina -= foundAbility.cost; | |
| } | |
| updateStats(); | |
| addToLog(`You use ${foundAbility.name}!`, 'blue'); | |
| // Simulate ability effect | |
| if (ability === 'power-strike' || ability === 'backstab' || ability === 'smite') { | |
| const damageRoll = ability === 'power-strike' ? rollDiceInBackground('2d8') : | |
| ability === 'backstab' ? rollDiceInBackground('3d6') : | |
| rollDiceInBackground('1d8 + 1d8'); | |
| setTimeout(() => { | |
| const damage = damageRoll; | |
| gameState.currentEnemy.hp -= damage; | |
| if (gameState.currentEnemy.hp < 0) gameState.currentEnemy.hp = 0; | |
| enemyHealth.textContent = `${gameState.currentEnemy.hp}/${gameState.currentEnemy.maxHp} HP`; | |
| enemyHpFill.style.width = `${(gameState.currentEnemy.hp / gameState.currentEnemy.maxHp) * 100}%`; | |
| addToLog(`${foundAbility.name} hits for ${damage} damage!`, 'yellow'); | |
| if (gameState.currentEnemy.hp <= 0) { | |
| enemyDefeated(); | |
| return; | |
| } | |
| // Enemy attack | |
| setTimeout(() => { | |
| enemyAttack(); | |
| }, 1000); | |
| }, 500); | |
| } else if (ability === 'healing-light') { | |
| const healRoll = rollDiceInBackground('2d8'); | |
| setTimeout(() => { | |
| const healAmount = healRoll; | |
| gameState.hp = Math.min(gameState.hp + healAmount, gameState.maxHp); | |
| updateStats(); | |
| addToLog(`${foundAbility.name} heals you for ${healAmount} HP!`, 'green'); | |
| // Enemy attack | |
| setTimeout(() => { | |
| enemyAttack(); | |
| }, 1000); | |
| }, 500); | |
| } else if (ability === 'fireball') { | |
| const damageRoll = rollDiceInBackground('3d6'); | |
| setTimeout(() => { | |
| const damage = damageRoll; | |
| gameState.currentEnemy.hp -= damage; | |
| if (gameState.currentEnemy.hp < 0) gameState.currentEnemy.hp = 0; | |
| enemyHealth.textContent = `${gameState.currentEnemy.hp}/${gameState.currentEnemy.maxHp} HP`; | |
| enemyHpFill.style.width = `${(gameState.currentEnemy.hp / gameState.currentEnemy.maxHp) * 100}%`; | |
| addToLog(`${foundAbility.name} burns the enemy for ${damage} damage!`, 'orange'); | |
| if (gameState.currentEnemy.hp <= 0) { | |
| enemyDefeated(); | |
| return; | |
| } | |
| // Enemy attack | |
| setTimeout(() => { | |
| enemyAttack(); | |
| }, 1000); | |
| }, 500); | |
| } else { | |
| // Default effect for other abilities | |
| addToLog(`${foundAbility.name}: ${foundAbility.effect}`, 'blue'); | |
| // Enemy attack | |
| setTimeout(() => { | |
| enemyAttack(); | |
| }, 1000); | |
| } | |
| } | |
| function useItem(itemName) { | |
| const itemIndex = gameState.inventory.findIndex(item => item.name === itemName); | |
| if (itemIndex === -1 || gameState.inventory[itemIndex].quantity <= 0) { | |
| addToLog(`You don't have any ${itemName}`, 'red'); | |
| return; | |
| } | |
| if (itemName === "Health Potion") { | |
| const healAmount = rollDiceInBackground('2d4+2'); | |
| gameState.inventory[itemIndex].quantity--; | |
| setTimeout(() => { | |
| gameState.hp = Math.min(gameState.hp + healAmount, gameState.maxHp); | |
| updateStats(); | |
| addToLog(`You drink a Health Potion and recover ${healAmount} HP!`, 'green'); | |
| if (gameState.inventory[itemIndex].quantity <= 0) { | |
| gameState.inventory.splice(itemIndex, 1); | |
| } | |
| }, 500); | |
| } else { | |
| addToLog(`You use the ${itemName}`, 'blue'); | |
| gameState.inventory[itemIndex].quantity--; | |
| if (gameState.inventory[itemIndex].quantity <= 0) { | |
| gameState.inventory.splice(itemIndex, 1); | |
| } | |
| } | |
| } | |
| function sellItems() { | |
| let totalGold = 0; | |
| // Sell all non-essential items | |
| const itemsToSell = gameState.inventory.filter(item => | |
| item.name !== "Health Potion" && | |
| item.name !== "Gold" && | |
| item.name !== "Old Key" | |
| ); | |
| if (itemsToSell.length === 0) { | |
| addToLog("You have nothing valuable to sell!", 'gray'); | |
| return; | |
| } | |
| itemsToSell.forEach(item => { | |
| let value = 0; | |
| switch(item.name) { | |
| case "Scroll of Fire": | |
| value = 20; | |
| break; | |
| case "Rations": | |
| value = 1 * item.quantity; | |
| break; | |
| case "Amethyst": | |
| value = 25; | |
| break; | |
| default: | |
| value = 10; | |
| } | |
| totalGold += value; | |
| addToLog(`Sold ${item.quantity > 1 ? item.quantity + ' ' + item.name + 's' : item.name} for ${value} gold.`, 'yellow'); | |
| }); | |
| // Remove sold items | |
| gameState.inventory = gameState.inventory.filter(item => | |
| item.name === "Health Potion" || | |
| item.name === "Gold" || | |
| item.name === "Old Key" | |
| ); | |
| // Add gold to inventory | |
| const goldIndex = gameState.inventory.findIndex(item => item.name === "Gold"); | |
| if (goldIndex !== -1) { | |
| gameState.inventory[goldIndex].quantity += totalGold; | |
| } else if (totalGold > 0) { | |
| gameState.inventory.push({ | |
| name: "Gold", | |
| icon: "fa-coins", | |
| color: "yellow-500", | |
| quantity: totalGold, | |
| description: "Shiny gold coins for purchasing items." | |
| }); | |
| } | |
| addToLog(`Total gained: ${totalGold} gold`, 'yellow'); | |
| } | |
| function showQuestDetails() { | |
| addToLog("=== Active Quests ===", 'yellow'); | |
| gameState.quests.filter(q => !q.completed).forEach(quest => { | |
| addToLog(`${quest.name}: ${quest.description}`); | |
| addToLog(`Reward: ${quest.reward}`); | |
| addToLog(`Progress: ${Math.floor(quest.progress * 100)}%`); | |
| addToLog("----------------", 'gray'); | |
| }); | |
| const completedQuests = gameState.quests.filter(q => q.completed); | |
| if (completedQuests.length > 0) { | |
| addToLog("=== Completed Quests ===", 'green'); | |
| completedQuests.forEach(quest => { | |
| addToLog(`${quest.name}`); | |
| }); | |
| } | |
| } | |
| function levelUp() { | |
| gameState.level++; | |
| gameState.xp -= gameState.xpToNextLevel; | |
| gameState.xpToNextLevel = Math.floor(gameState.xpToNextLevel * 1.5); | |
| // Restore all resources | |
| gameState.hp = gameState.maxHp; | |
| gameState.mana = gameState.maxMana; | |
| gameState.stamina = gameState.maxStamina; | |
| // Increase max resources slightly | |
| gameState.maxHp += 10; | |
| gameState.maxMana += 5; | |
| gameState.maxStamina += 8; | |
| newLevel.textContent = gameState.level; | |
| levelUpModal.classList.remove('hidden'); | |
| addToLog(`Congratulations! You reached level ${gameState.level}!`, 'yellow'); | |
| } | |
| function levelUpAttribute(attribute) { | |
| const character = characterClasses[gameState.character]; | |
| switch(attribute) { | |
| case 'strength': | |
| character.stats.str++; | |
| addToLog("Your strength increases!", 'yellow'); | |
| break; | |
| case 'dexterity': | |
| character.stats.dex++; | |
| addToLog("Your dexterity increases!", 'yellow'); | |
| break; | |
| case 'intellect': | |
| character.stats.int++; | |
| addToLog("Your intellect increases!", 'yellow'); | |
| break; | |
| } | |
| updateStats(); | |
| levelUpModal.classList.add('hidden'); | |
| // Fully restore stats | |
| gameState.hp = gameState.maxHp; | |
| gameState.mana = gameState.maxMana; | |
| gameState.stamina = gameState.maxStamina; | |
| updateStats(); | |
| } | |
| function gameOver() { | |
| gameState.inCombat = false; | |
| gameOverModal.classList.remove('hidden'); | |
| addToLog("You have been defeated...", 'red'); | |
| } | |
| function retryGame() { | |
| gameOverModal.classList.add('hidden'); | |
| startGame(); | |
| } | |
| function newCharacter() { | |
| gameOverModal.classList.add('hidden'); | |
| gameArea.classList.add('hidden'); | |
| characterSelection.classList.remove('hidden'); | |
| selectedCharacterInfo.classList.add('hidden'); | |
| // Remove selection highlight | |
| document.querySelectorAll('.character-card').forEach(card => { | |
| card.classList.remove('border-yellow-400', 'bg-gray-600'); | |
| }); | |
| gameState.character = null; | |
| } | |
| function rollDice(diceType) { | |
| diceAnimation.classList.remove('hidden'); | |
| diceResultContainer.classList.add('hidden'); | |
| setTimeout(() => { | |
| const result = rollDiceInBackground(diceType); | |
| diceAnimation.classList.add('hidden'); | |
| diceResultContainer.classList.remove('hidden'); | |
| diceResult.textContent = result; | |
| diceType.textContent = diceType; | |
| addToLog(`Rolled ${diceType}: ${result}`, 'gray'); | |
| }, 1000); | |
| } | |
| function rollDiceInBackground(diceString) { | |
| // Parse dice string like "2d8+3" or "d20" | |
| const parts = diceString.split(/[d+-]/); | |
| let numDice = 1; | |
| let numSides = 20; | |
| let modifier = 0; | |
| if (parts[0] !== '') { | |
| numDice = parseInt(parts[0]); | |
| } | |
| if (parts.length > 1) { | |
| numSides = parseInt(parts[1]); | |
| } | |
| if (diceString.includes('+') && parts.length > 2) { | |
| modifier = parseInt(parts[2]); | |
| } else if (diceString.includes('-') && parts.length > 2) { | |
| modifier = -parseInt(parts[2]); | |
| } | |
| let total = modifier; | |
| for (let i = 0; i < numDice; i++) { | |
| total += Math.floor(Math.random() * numSides) + 1; | |
| } | |
| return total; | |
| } | |
| function addToLog(message, color = 'white') { | |
| const logEntry = document.createElement('div'); | |
| switch(color) { | |
| case 'red': | |
| logEntry.className = 'text-red-400'; | |
| break; | |
| case 'green': | |
| logEntry.className = 'text-green-400'; | |
| break; | |
| case 'blue': | |
| logEntry.className = 'text-blue-400'; | |
| break; | |
| case 'yellow': | |
| logEntry.className = 'text-yellow-400'; | |
| break; | |
| case 'orange': | |
| logEntry.className = 'text-orange-400'; | |
| break; | |
| case 'purple': | |
| logEntry.className = 'text-purple-400'; | |
| break; | |
| case 'gray': | |
| logEntry.className = 'text-gray-400'; | |
| break; | |
| default: | |
| logEntry.className = 'text-white'; | |
| } | |
| logEntry.textContent = message; | |
| gameLog.appendChild(logEntry); | |
| // Auto-scroll to bottom | |
| gameLog.scrollTop = gameLog.scrollHeight; | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=austrian11/austrian-game-idea" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |