Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tactical Legends - Enhanced</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> | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| .pulse-animation { | |
| animation: pulse 2s infinite; | |
| } | |
| .character-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .character-card { | |
| transition: all 0.3s ease; | |
| } | |
| .health-bar { | |
| height: 6px; | |
| border-radius: 3px; | |
| transition: width 0.5s ease; | |
| } | |
| .mana-bar { | |
| height: 4px; | |
| border-radius: 2px; | |
| transition: width 0.5s ease; | |
| } | |
| .battle-log { | |
| max-height: 200px; | |
| overflow-y: auto; | |
| scrollbar-width: thin; | |
| } | |
| .battle-log::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .battle-log::-webkit-scrollbar-thumb { | |
| background-color: rgba(255,255,255,0.2); | |
| border-radius: 3px; | |
| } | |
| .tooltip { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .tooltip .tooltip-text { | |
| visibility: hidden; | |
| width: 200px; | |
| background-color: #1F2937; | |
| color: #fff; | |
| text-align: center; | |
| border-radius: 6px; | |
| padding: 5px; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 125%; | |
| left: 50%; | |
| margin-left: -100px; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .tooltip:hover .tooltip-text { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| .skill-icon { | |
| transition: all 0.2s ease; | |
| } | |
| .skill-icon:hover { | |
| transform: scale(1.2); | |
| } | |
| .skill-icon:active { | |
| transform: scale(0.95); | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 75% { transform: translateX(5px); } | |
| } | |
| .shake { | |
| animation: shake 0.5s; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="mb-10 text-center"> | |
| <h1 class="text-4xl md:text-6xl font-bold bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent mb-2"> | |
| TACTICAL LEGENDS | |
| </h1> | |
| <p class="text-lg text-gray-300 max-w-2xl mx-auto"> | |
| A strategic battle game where you assemble a team of heroes to defeat powerful enemies | |
| </p> | |
| </header> | |
| <!-- Game Container --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Player Team --> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-lg border border-gray-700"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-bold text-blue-400">Your Team</h2> | |
| <div class="flex space-x-2"> | |
| <button id="new-game-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition"> | |
| <i class="fas fa-plus mr-2"></i>New Game | |
| </button> | |
| <button id="heal-all-btn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition"> | |
| <i class="fas fa-heart mr-2"></i>Heal All | |
| </button> | |
| </div> | |
| </div> | |
| <div id="player-team" class="space-y-4"> | |
| <!-- Character cards will be inserted here by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- Battle Area --> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-lg border border-gray-700"> | |
| <h2 class="text-2xl font-bold text-purple-400 mb-6 text-center">Battle Arena</h2> | |
| <div class="flex flex-col items-center justify-center mb-6"> | |
| <div id="battle-animation" class="h-40 w-full flex items-center justify-center mb-4"> | |
| <div class="text-center"> | |
| <i class="fas fa-swords text-4xl text-gray-500"></i> | |
| <p class="text-gray-400 mt-2">Select a character and action to begin</p> | |
| </div> | |
| </div> | |
| <div id="battle-controls" class="grid grid-cols-2 gap-4 w-full max-w-md"> | |
| <button id="attack-btn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-3 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-fist-raised mr-2"></i> Basic Attack | |
| </button> | |
| <button id="skill1-btn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-3 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-fire mr-2"></i> Fireball | |
| </button> | |
| <button id="skill2-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-3 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-shield-alt mr-2"></i> Shield | |
| </button> | |
| <button id="skill3-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-3 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-bolt mr-2"></i> Lightning | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-gray-900 rounded-lg p-4"> | |
| <h3 class="text-lg font-semibold text-gray-300 mb-2">Battle Log</h3> | |
| <div id="battle-log" class="battle-log text-sm space-y-1"> | |
| <!-- Battle messages will appear here --> | |
| <div class="text-gray-400 italic">Waiting for battle to begin...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Enemy Team --> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-lg border border-gray-700"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-bold text-red-400">Enemies</h2> | |
| <div class="text-gray-400"> | |
| Wave: <span id="wave-count" class="font-bold text-white">1</span> | |
| </div> | |
| </div> | |
| <div id="enemy-team" class="space-y-4"> | |
| <!-- Enemy cards will be inserted here by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Game Stats --> | |
| <div class="mt-10 bg-gray-800 rounded-xl p-6 shadow-lg border border-gray-700"> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-gray-900 p-4 rounded-lg"> | |
| <h3 class="text-lg font-semibold text-blue-400 mb-2">Game Stats</h3> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Battles Won:</span> | |
| <span id="battles-won" class="font-bold">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Enemies Defeated:</span> | |
| <span id="enemies-defeated" class="font-bold">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Total Damage:</span> | |
| <span id="total-damage" class="font-bold">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-900 p-4 rounded-lg"> | |
| <h3 class="text-lg font-semibold text-purple-400 mb-2">Team Status</h3> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Alive:</span> | |
| <span id="team-alive" class="font-bold text-green-400">0/3</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Average Health:</span> | |
| <span id="avg-health" class="font-bold">0%</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Average Mana:</span> | |
| <span id="avg-mana" class="font-bold">0%</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-900 p-4 rounded-lg"> | |
| <h3 class="text-lg font-semibold text-yellow-400 mb-2">Skills Used</h3> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Basic Attacks:</span> | |
| <span id="basic-attacks" class="font-bold">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Fireballs:</span> | |
| <span id="fireballs-used" class="font-bold">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-400">Shields:</span> | |
| <span id="shields-used" class="font-bold">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game State | |
| const gameState = { | |
| playerTeam: [], | |
| enemyTeam: [], | |
| selectedCharacter: null, | |
| selectedEnemy: null, | |
| wave: 1, | |
| stats: { | |
| battlesWon: 0, | |
| enemiesDefeated: 0, | |
| totalDamage: 0, | |
| basicAttacks: 0, | |
| fireballsUsed: 0, | |
| shieldsUsed: 0, | |
| lightningsUsed: 0 | |
| }, | |
| gameActive: false | |
| }; | |
| // Character Classes | |
| class Character { | |
| constructor(name, health, mana, attack, defense, speed, skills, image) { | |
| this.name = name; | |
| this.maxHealth = health; | |
| this.health = health; | |
| this.maxMana = mana; | |
| this.mana = mana; | |
| this.attack = attack; | |
| this.defense = defense; | |
| this.speed = speed; | |
| this.skills = skills; | |
| this.image = image; | |
| this.alive = true; | |
| this.shield = 0; | |
| } | |
| takeDamage(damage) { | |
| const actualDamage = Math.max(0, damage - this.shield); | |
| this.health = Math.max(0, this.health - actualDamage); | |
| this.shield = Math.max(0, this.shield - damage); | |
| if (this.health <= 0) { | |
| this.alive = false; | |
| } | |
| return actualDamage; | |
| } | |
| useMana(amount) { | |
| if (this.mana >= amount) { | |
| this.mana -= amount; | |
| return true; | |
| } | |
| return false; | |
| } | |
| heal(amount) { | |
| this.health = Math.min(this.maxHealth, this.health + amount); | |
| } | |
| restoreMana(amount) { | |
| this.mana = Math.min(this.maxMana, this.mana + amount); | |
| } | |
| addShield(amount) { | |
| this.shield += amount; | |
| } | |
| basicAttack(target) { | |
| const damage = Math.max(1, this.attack - Math.floor(target.defense / 2)); | |
| const actualDamage = target.takeDamage(damage); | |
| gameState.stats.basicAttacks++; | |
| gameState.stats.totalDamage += actualDamage; | |
| return { damage: actualDamage, critical: false }; | |
| } | |
| useSkill(skillIndex, target) { | |
| const skill = this.skills[skillIndex]; | |
| if (!skill || !this.useMana(skill.manaCost)) return null; | |
| let result = { damage: 0, effect: null }; | |
| switch(skillIndex) { | |
| case 0: // Fireball | |
| const fireDamage = Math.max(1, this.attack * 1.5 - Math.floor(target.defense / 3)); | |
| result.damage = target.takeDamage(fireDamage); | |
| gameState.stats.fireballsUsed++; | |
| gameState.stats.totalDamage += result.damage; | |
| break; | |
| case 1: // Shield | |
| this.addShield(skill.effectAmount); | |
| result.effect = `gained ${skill.effectAmount} shield`; | |
| gameState.stats.shieldsUsed++; | |
| break; | |
| case 2: // Lightning | |
| const lightningDamage = Math.max(1, this.attack * 2 - Math.floor(target.defense / 4)); | |
| result.damage = target.takeDamage(lightningDamage); | |
| gameState.stats.lightningsUsed++; | |
| gameState.stats.totalDamage += result.damage; | |
| result.effect = "stunned the target"; | |
| break; | |
| } | |
| return result; | |
| } | |
| } | |
| // Initialize the game | |
| function initGame() { | |
| gameState.gameActive = true; | |
| gameState.playerTeam = [ | |
| new Character("Warrior", 120, 50, 15, 12, 8, [ | |
| { name: "Fireball", manaCost: 15, effectAmount: 0 }, | |
| { name: "Shield", manaCost: 10, effectAmount: 10 }, | |
| { name: "Lightning", manaCost: 25, effectAmount: 0 } | |
| ], "warrior.png"), | |
| new Character("Mage", 80, 100, 20, 6, 10, [ | |
| { name: "Fireball", manaCost: 10, effectAmount: 0 }, | |
| { name: "Shield", manaCost: 15, effectAmount: 5 }, | |
| { name: "Lightning", manaCost: 20, effectAmount: 0 } | |
| ], "mage.png"), | |
| new Character("Rogue", 90, 70, 18, 8, 15, [ | |
| { name: "Fireball", manaCost: 12, effectAmount: 0 }, | |
| { name: "Shield", manaCost: 8, effectAmount: 8 }, | |
| { name: "Lightning", manaCost: 18, effectAmount: 0 } | |
| ], "rogue.png") | |
| ]; | |
| spawnEnemyWave(); | |
| renderTeams(); | |
| updateStats(); | |
| addToBattleLog("Game started! Select a character and enemy to begin."); | |
| } | |
| // Spawn enemies for current wave | |
| function spawnEnemyWave() { | |
| gameState.enemyTeam = []; | |
| const enemyCount = Math.min(3, 1 + Math.floor(gameState.wave / 2)); | |
| for (let i = 0; i < enemyCount; i++) { | |
| const enemyTypes = ["Goblin", "Orc", "Skeleton", "Troll"]; | |
| const type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; | |
| let enemy; | |
| switch(type) { | |
| case "Goblin": | |
| enemy = new Character("Goblin", 60 + gameState.wave * 5, 30, 10 + gameState.wave, 6 + gameState.wave, 12, [ | |
| { name: "Bite", manaCost: 5, effectAmount: 0 }, | |
| { name: "Dodge", manaCost: 8, effectAmount: 5 } | |
| ], "goblin.png"); | |
| break; | |
| case "Orc": | |
| enemy = new Character("Orc", 100 + gameState.wave * 8, 20, 15 + gameState.wave * 2, 10 + gameState.wave, 6, [ | |
| { name: "Smash", manaCost: 10, effectAmount: 0 }, | |
| { name: "Roar", manaCost: 5, effectAmount: 3 } | |
| ], "orc.png"); | |
| break; | |
| case "Skeleton": | |
| enemy = new Character("Skeleton", 70 + gameState.wave * 6, 40, 12 + gameState.wave, 8 + gameState.wave, 10, [ | |
| { name: "Bone Throw", manaCost: 8, effectAmount: 0 }, | |
| { name: "Dodge", manaCost: 6, effectAmount: 4 } | |
| ], "skeleton.png"); | |
| break; | |
| case "Troll": | |
| enemy = new Character("Troll", 150 + gameState.wave * 10, 10, 18 + gameState.wave * 2, 5 + gameState.wave, 4, [ | |
| { name: "Regenerate", manaCost: 15, effectAmount: 15 }, | |
| { name: "Club Swing", manaCost: 12, effectAmount: 0 } | |
| ], "troll.png"); | |
| break; | |
| } | |
| gameState.enemyTeam.push(enemy); | |
| } | |
| } | |
| // Render player and enemy teams | |
| function renderTeams() { | |
| renderPlayerTeam(); | |
| renderEnemyTeam(); | |
| } | |
| function renderPlayerTeam() { | |
| const container = document.getElementById('player-team'); | |
| container.innerHTML = ''; | |
| gameState.playerTeam.forEach((char, index) => { | |
| const card = document.createElement('div'); | |
| card.className = `character-card bg-gray-700 rounded-lg p-4 cursor-pointer transition ${!char.alive ? 'opacity-50' : ''} ${gameState.selectedCharacter === index ? 'ring-2 ring-blue-500' : ''}`; | |
| card.innerHTML = ` | |
| <div class="flex items-center space-x-4"> | |
| <div class="w-16 h-16 rounded-full bg-gray-600 flex items-center justify-center overflow-hidden"> | |
| <i class="fas fa-user text-3xl text-gray-400"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="flex justify-between items-center mb-1"> | |
| <h3 class="font-bold ${!char.alive ? 'text-gray-400' : 'text-blue-300'}">${char.name}</h3> | |
| <span class="text-sm ${char.alive ? 'text-green-400' : 'text-red-400'}">${char.alive ? 'ALIVE' : 'DEFEATED'}</span> | |
| </div> | |
| <div class="mb-1"> | |
| <div class="flex justify-between text-xs mb-1"> | |
| <span>HP: ${char.health}/${char.maxHealth}</span> | |
| <span>${Math.floor((char.health / char.maxHealth) * 100)}%</span> | |
| </div> | |
| <div class="health-bar bg-gray-600 rounded-full"> | |
| <div class="health-bar bg-red-500 rounded-full" style="width: ${(char.health / char.maxHealth) * 100}%"></div> | |
| </div> | |
| </div> | |
| <div class="mb-1"> | |
| <div class="flex justify-between text-xs mb-1"> | |
| <span>MP: ${char.mana}/${char.maxMana}</span> | |
| <span>${Math.floor((char.mana / char.maxMana) * 100)}%</span> | |
| </div> | |
| <div class="mana-bar bg-gray-600 rounded-full"> | |
| <div class="mana-bar bg-blue-500 rounded-full" style="width: ${(char.mana / char.maxMana) * 100}%"></div> | |
| </div> | |
| </div> | |
| <div class="flex justify-between text-xs"> | |
| <span>ATK: ${char.attack}</span> | |
| <span>DEF: ${char.defense}</span> | |
| <span>SPD: ${char.speed}</span> | |
| </div> | |
| </div> | |
| </div> | |
| ${char.shield > 0 ? `<div class="mt-2 text-xs text-yellow-400"><i class="fas fa-shield-alt mr-1"></i> Shield: ${char.shield}</div>` : ''} | |
| `; | |
| card.addEventListener('click', () => selectCharacter(index)); | |
| container.appendChild(card); | |
| }); | |
| } | |
| function renderEnemyTeam() { | |
| const container = document.getElementById('enemy-team'); | |
| container.innerHTML = ''; | |
| gameState.enemyTeam.forEach((enemy, index) => { | |
| const card = document.createElement('div'); | |
| card.className = `character-card bg-gray-700 rounded-lg p-4 cursor-pointer transition ${!enemy.alive ? 'opacity-50' : ''} ${gameState.selectedEnemy === index ? 'ring-2 ring-red-500' : ''}`; | |
| card.innerHTML = ` | |
| <div class="flex items-center space-x-4"> | |
| <div class="w-16 h-16 rounded-full bg-gray-600 flex items-center justify-center overflow-hidden"> | |
| <i class="fas fa-skull text-3xl text-gray-400"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="flex justify-between items-center mb-1"> | |
| <h3 class="font-bold ${!enemy.alive ? 'text-gray-400' : 'text-red-300'}">${enemy.name}</h3> | |
| <span class="text-sm ${enemy.alive ? 'text-red-400' : 'text-green-400'}">${enemy.alive ? 'THREAT' : 'DEFEATED'}</span> | |
| </div> | |
| <div class="mb-1"> | |
| <div class="flex justify-between text-xs mb-1"> | |
| <span>HP: ${enemy.health}/${enemy.maxHealth}</span> | |
| <span>${Math.floor((enemy.health / enemy.maxHealth) * 100)}%</span> | |
| </div> | |
| <div class="health-bar bg-gray-600 rounded-full"> | |
| <div class="health-bar bg-red-500 rounded-full" style="width: ${(enemy.health / enemy.maxHealth) * 100}%"></div> | |
| </div> | |
| </div> | |
| <div class="mb-1"> | |
| <div class="flex justify-between text-xs mb-1"> | |
| <span>MP: ${enemy.mana}/${enemy.maxMana}</span> | |
| <span>${Math.floor((enemy.mana / enemy.maxMana) * 100)}%</span> | |
| </div> | |
| <div class="mana-bar bg-gray-600 rounded-full"> | |
| <div class="mana-bar bg-blue-500 rounded-full" style="width: ${(enemy.mana / enemy.maxMana) * 100}%"></div> | |
| </div> | |
| </div> | |
| <div class="flex justify-between text-xs"> | |
| <span>ATK: ${enemy.attack}</span> | |
| <span>DEF: ${enemy.defense}</span> | |
| <span>SPD: ${enemy.speed}</span> | |
| </div> | |
| </div> | |
| </div> | |
| ${enemy.shield > 0 ? `<div class="mt-2 text-xs text-yellow-400"><i class="fas fa-shield-alt mr-1"></i> Shield: ${enemy.shield}</div>` : ''} | |
| `; | |
| card.addEventListener('click', () => selectEnemy(index)); | |
| container.appendChild(card); | |
| }); | |
| } | |
| // Select characters | |
| function selectCharacter(index) { | |
| const char = gameState.playerTeam[index]; | |
| if (!char.alive) return; | |
| gameState.selectedCharacter = index; | |
| renderTeams(); | |
| // Enable skill buttons based on mana | |
| updateSkillButtons(); | |
| addToBattleLog(`Selected ${char.name} as your active character.`); | |
| } | |
| function selectEnemy(index) { | |
| const enemy = gameState.enemyTeam[index]; | |
| if (!enemy.alive) return; | |
| gameState.selectedEnemy = index; | |
| renderTeams(); | |
| addToBattleLog(`Targeted ${enemy.name} for your next action.`); | |
| } | |
| // Update skill buttons based on selected character's mana | |
| function updateSkillButtons() { | |
| if (gameState.selectedCharacter === null) return; | |
| const char = gameState.playerTeam[gameState.selectedCharacter]; | |
| document.getElementById('attack-btn').disabled = false; | |
| document.getElementById('skill1-btn').disabled = char.mana < char.skills[0].manaCost; | |
| document.getElementById('skill2-btn').disabled = char.mana < char.skills[1].manaCost; | |
| document.getElementById('skill3-btn').disabled = char.mana < char.skills[2].manaCost; | |
| // Update tooltips | |
| document.getElementById('skill1-btn').title = `${char.skills[0].name} (${char.skills[0].manaCost} MP)`; | |
| document.getElementById('skill2-btn').title = `${char.skills[1].name} (${char.skills[1].manaCost} MP)`; | |
| document.getElementById('skill3-btn').title = `${char.skills[2].name} (${char.skills[2].manaCost} MP)`; | |
| } | |
| // Battle actions | |
| function playerAttack() { | |
| if (gameState.selectedCharacter === null || gameState.selectedEnemy === null) { | |
| addToBattleLog("Please select both a character and an enemy first!", "red-400"); | |
| return; | |
| } | |
| const char = gameState.playerTeam[gameState.selectedCharacter]; | |
| const enemy = gameState.enemyTeam[gameState.selectedEnemy]; | |
| if (!char.alive || !enemy.alive) { | |
| addToBattleLog("Cannot attack with a defeated character or target a defeated enemy!", "red-400"); | |
| return; | |
| } | |
| const result = char.basicAttack(enemy); | |
| animateBattle(char, enemy, result.damage, "basic"); | |
| addToBattleLog(`${char.name} attacked ${enemy.name} for ${result.damage} damage!`, "blue-400"); | |
| processEnemyTurn(); | |
| } | |
| function playerUseSkill(skillIndex) { | |
| if (gameState.selectedCharacter === null || gameState.selectedEnemy === null) { | |
| addToBattleLog("Please select both a character and an enemy first!", "red-400"); | |
| return; | |
| } | |
| const char = gameState.playerTeam[gameState.selectedCharacter]; | |
| const enemy = gameState.enemyTeam[gameState.selectedEnemy]; | |
| if (!char.alive || !enemy.alive) { | |
| addToBattleLog("Cannot use skills with a defeated character or target a defeated enemy!", "red-400"); | |
| return; | |
| } | |
| const result = char.useSkill(skillIndex, enemy); | |
| if (!result) { | |
| addToBattleLog(`${char.name} doesn't have enough mana for ${char.skills[skillIndex].name}!`, "red-400"); | |
| return; | |
| } | |
| animateBattle(char, enemy, result.damage, "skill"); | |
| let message = `${char.name} used ${char.skills[skillIndex].name} on ${enemy.name}`; | |
| if (result.damage > 0) message += ` for ${result.damage} damage!`; | |
| if (result.effect) message += ` and ${result.effect}!`; | |
| addToBattleLog(message, "purple-400"); | |
| processEnemyTurn(); | |
| } | |
| function processEnemyTurn() { | |
| // Check if all enemies are defeated | |
| if (gameState.enemyTeam.every(e => !e.alive)) { | |
| endWave(); | |
| return; | |
| } | |
| // Check if all players are defeated | |
| if (gameState.playerTeam.every(p => !p.alive)) { | |
| gameOver(); | |
| return; | |
| } | |
| // Enemy AI: Simple random actions | |
| setTimeout(() => { | |
| gameState.enemyTeam.forEach((enemy, enemyIndex) => { | |
| if (!enemy.alive) return; | |
| // Find a random alive player character | |
| const alivePlayers = gameState.playerTeam | |
| .map((char, idx) => ({ char, idx })) | |
| .filter(c => c.char.alive); | |
| if (alivePlayers.length === 0) return; | |
| const target = alivePlayers[Math.floor(Math.random() * alivePlayers.length)]; | |
| const playerChar = target.char; | |
| const playerIndex = target.idx; | |
| // Randomly choose between basic attack and skills (if has mana) | |
| const possibleActions = ['attack']; | |
| enemy.skills.forEach((skill, idx) => { | |
| if (enemy.mana >= skill.manaCost) { | |
| possibleActions.push(`skill${idx}`); | |
| } | |
| }); | |
| const action = possibleActions[Math.floor(Math.random() * possibleActions.length)]; | |
| let result, message; | |
| switch(action) { | |
| case 'attack': | |
| result = enemy.basicAttack(playerChar); | |
| message = `${enemy.name} attacked ${playerChar.name} for ${result.damage} damage!`; | |
| break; | |
| case 'skill0': | |
| result = enemy.useSkill(0, playerChar); | |
| message = `${enemy.name} used ${enemy.skills[0].name} on ${playerChar.name} for ${result.damage} damage!`; | |
| break; | |
| case 'skill1': | |
| result = enemy.useSkill(1, playerChar); | |
| message = `${enemy.name} used ${enemy.skills[1].name} and ${result.effect}!`; | |
| break; | |
| } | |
| animateBattle(enemy, playerChar, result?.damage || 0, "enemy"); | |
| addToBattleLog(message, "red-400"); | |
| // Update UI after enemy turn | |
| renderTeams(); | |
| updateStats(); | |
| // Check again if all players are defeated after enemy turn | |
| if (gameState.playerTeam.every(p => !p.alive)) { | |
| gameOver(); | |
| } | |
| }); | |
| }, 1000); | |
| } | |
| // Battle animations | |
| function animateBattle(attacker, defender, damage, type) { | |
| const animationContainer = document.getElementById('battle-animation'); | |
| animationContainer.innerHTML = ''; | |
| const attackerImg = document.createElement('div'); | |
| attackerImg.className = 'text-5xl mx-4'; | |
| attackerImg.innerHTML = type === 'enemy' ? '<i class="fas fa-skull"></i>' : '<i class="fas fa-user"></i>'; | |
| const defenderImg = document.createElement('div'); | |
| defenderImg.className = 'text-5xl mx-4 shake'; | |
| defenderImg.innerHTML = type !== 'enemy' ? '<i class="fas fa-skull"></i>' : '<i class="fas fa-user"></i>'; | |
| const effect = document.createElement('div'); | |
| effect.className = 'text-4xl mx-4 fade-in'; | |
| switch(type) { | |
| case 'basic': | |
| effect.innerHTML = '<i class="fas fa-fist-raised text-red-500"></i>'; | |
| break; | |
| case 'skill': | |
| effect.innerHTML = '<i class="fas fa-fire text-yellow-500"></i>'; | |
| break; | |
| case 'enemy': | |
| effect.innerHTML = '<i class="fas fa-bolt text-purple-500"></i>'; | |
| break; | |
| } | |
| animationContainer.appendChild(attackerImg); | |
| animationContainer.appendChild(effect); | |
| animationContainer.appendChild(defenderImg); | |
| if (damage > 0) { | |
| setTimeout(() => { | |
| const damageText = document.createElement('div'); | |
| damageText.className = 'absolute text-xl font-bold text-red-500 fade-in'; | |
| damageText.textContent = `-${damage}`; | |
| defenderImg.appendChild(damageText); | |
| setTimeout(() => { | |
| damageText.remove(); | |
| }, 1000); | |
| }, 300); | |
| } | |
| // Reset animation after delay | |
| setTimeout(() => { | |
| animationContainer.innerHTML = '<div class="text-center"><i class="fas fa-swords text-4xl text-gray-500"></i><p class="text-gray-400 mt-2">Select a character and action to continue</p></div>'; | |
| }, 1500); | |
| } | |
| // End of wave | |
| function endWave() { | |
| gameState.stats.battlesWon++; | |
| gameState.wave++; | |
| document.getElementById('wave-count').textContent = gameState.wave; | |
| // Heal team a bit between waves | |
| gameState.playerTeam.forEach(char => { | |
| if (char.alive) { | |
| char.heal(Math.floor(char.maxHealth * 0.3)); | |
| char.restoreMana(Math.floor(char.maxMana * 0.2)); | |
| } | |
| }); | |
| spawnEnemyWave(); | |
| renderTeams(); | |
| updateStats(); | |
| addToBattleLog(`Wave ${gameState.wave - 1} cleared! Starting wave ${gameState.wave}.`, "green-400"); | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameState.gameActive = false; | |
| addToBattleLog("GAME OVER! Your team has been defeated.", "red-400"); | |
| document.getElementById('attack-btn').disabled = true; | |
| document.getElementById('skill1-btn').disabled = true; | |
| document.getElementById('skill2-btn').disabled = true; | |
| document.getElementById('skill3-btn').disabled = true; | |
| } | |
| // Heal all characters | |
| function healAll() { | |
| if (!gameState.gameActive) { | |
| addToBattleLog("Start a new game first!", "red-400"); | |
| return; | |
| } | |
| gameState.playerTeam.forEach(char => { | |
| if (char.alive) { | |
| char.heal(char.maxHealth); | |
| char.restoreMana(char.maxMana); | |
| } | |
| }); | |
| renderTeams(); | |
| updateStats(); | |
| addToBattleLog("All alive characters have been fully healed!", "green-400"); | |
| } | |
| // Update game stats display | |
| function updateStats() { | |
| document.getElementById('battles-won').textContent = gameState.stats.battlesWon; | |
| document.getElementById('enemies-defeated').textContent = gameState.stats.enemiesDefeated; | |
| document.getElementById('total-damage').textContent = gameState.stats.totalDamage; | |
| const aliveCount = gameState.playerTeam.filter(c => c.alive).length; | |
| document.getElementById('team-alive').textContent = `${aliveCount}/${gameState.playerTeam.length}`; | |
| // Calculate average health and mana | |
| let totalHealth = 0, totalMana = 0; | |
| let aliveChars = 0; | |
| gameState.playerTeam.forEach(char => { | |
| if (char.alive) { | |
| totalHealth += (char.health / char.maxHealth) * 100; | |
| totalMana += (char.mana / char.maxMana) * 100; | |
| aliveChars++; | |
| } | |
| }); | |
| const avgHealth = aliveChars > 0 ? Math.floor(totalHealth / aliveChars) : 0; | |
| const avgMana = aliveChars > 0 ? Math.floor(totalMana / aliveChars) : 0; | |
| document.getElementById('avg-health').textContent = `${avgHealth}%`; | |
| document.getElementById('avg-mana').textContent = `${avgMana}%`; | |
| // Skill usage | |
| document.getElementById('basic-attacks').textContent = gameState.stats.basicAttacks; | |
| document.getElementById('fireballs-used').textContent = gameState.stats.fireballsUsed; | |
| document.getElementById('shields-used').textContent = gameState.stats.shieldsUsed; | |
| } | |
| // Battle log | |
| function addToBattleLog(message, color = "gray-400") { | |
| const log = document.getElementById('battle-log'); | |
| const entry = document.createElement('div'); | |
| entry.className = `fade-in text-${color}`; | |
| entry.textContent = message; | |
| log.insertBefore(entry, log.firstChild); | |
| // Limit log entries | |
| if (log.children.length > 20) { | |
| log.removeChild(log.lastChild); | |
| } | |
| } | |
| // Event listeners | |
| document.getElementById('new-game-btn').addEventListener('click', initGame); | |
| document.getElementById('heal-all-btn').addEventListener('click', healAll); | |
| document.getElementById('attack-btn').addEventListener('click', playerAttack); | |
| document.getElementById('skill1-btn').addEventListener('click', () => playerUseSkill(0)); | |
| document.getElementById('skill2-btn').addEventListener('click', () => playerUseSkill(1)); | |
| document.getElementById('skill3-btn').addEventListener('click', () => playerUseSkill(2)); | |
| // Initialize UI | |
| document.getElementById('attack-btn').disabled = true; | |
| document.getElementById('skill1-btn').disabled = true; | |
| document.getElementById('skill2-btn').disabled = true; | |
| document.getElementById('skill3-btn').disabled = true; | |
| </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=jurgenpaul82/oistarian" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |