Created
November 11, 2025 23:29
-
-
Save StringManolo/ba54841c54e8ffa047ad8f1f3a3a42dd to your computer and use it in GitHub Desktop.
Gemini 2.5 flash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { getGame } from "./GameConfig"; // Placeholder for actual Game config import | |
| // --- Stat and Character Models --- | |
| class BattleCharacter { | |
| constructor(name, maxHp, attack, magic, defense, atbRate) { | |
| this.name = name; | |
| this.maxHp = maxHp; | |
| this.currentHp = maxHp; | |
| this.attack = attack; | |
| this.magic = magic; | |
| this.defense = defense; | |
| this.atbRate = atbRate; // Rate at which ATB bar fills (e.g., 5) | |
| this.atbValue = 0; // Current ATB value (0 to 100) | |
| this.isDefending = false; | |
| } | |
| } | |
| class BattleEnemy extends BattleCharacter { | |
| constructor(config) { | |
| super(config.name, config.hp, config.attack, config.magic, config.defense, config.atbRate); | |
| this.key = config.key; | |
| } | |
| } | |
| // --- Phaser Battle Scene --- | |
| export class BattleScene extends Phaser.Scene { | |
| constructor() { | |
| super({ key: 'BattleScene' }); | |
| this.battleState = 'INIT'; | |
| this.uiElements = []; | |
| } | |
| init(data) { | |
| this.playerData = data.playerData || { | |
| name: 'Warrior', | |
| maxHp: 200, | |
| attack: 30, | |
| magic: 15, | |
| defense: 10, | |
| atbRate: 5 // Default ATB rate | |
| }; | |
| this.onBattleEnd = data.onBattleEnd; | |
| this.terrain = data.terrain; | |
| } | |
| preload() { | |
| // Load battle backgrounds | |
| this.load.image('battle_grass', 'assets/images/battles/battle_grass.png'); | |
| this.load.image('battle_forest', 'assets/images/battles/battle_forest.png'); | |
| this.load.image('battle_mountain', 'assets/images/battles/battle_mountain.png'); | |
| this.load.image('battle_dirt', 'assets/images/battles/battle_dirt.png'); | |
| // Load basic enemies | |
| // TODO: Create sprites for enemies animations | |
| this.load.image('goat', 'assets/images/enemies/goat.png'); | |
| this.load.image('war_dwarf', 'assets/images/enemies/war_dwarf.png'); | |
| this.load.image('snake', 'assets/images/enemies/snake.png'); | |
| this.load.image('wolf', 'assets/images/enemies/wolf.png'); | |
| this.load.image('fly', 'assets/images/enemies/fly.png'); | |
| this.load.image('war_blob', 'assets/images/enemies/war_blob.png'); | |
| this.load.image('war_orc', 'assets/images/enemies/war_orc.png'); | |
| this.load.image('magic_demon', 'assets/images/enemies/magic_demon.png'); | |
| // High quality assets for warrior attack animation (assuming the original image assets are used) | |
| this.load.image("warrior_basic_attack", "assets/images/battleAnimations/warrior_basic_1.png"); | |
| this.load.image("warrior_basic_attack_2", "assets/images/battleAnimations/warrior_basic_2.png"); | |
| this.load.image("warrior_basic_attack_3", "assets/images/battleAnimations/warrior_basic_3.png"); | |
| this.load.image("warrior_basic_attack_4", "assets/images/battleAnimations/warrior_basic_4.png"); | |
| // Sound effect for attack | |
| this.load.audio("warrior_slash", "assets/audio/warrior_slash.wav"); | |
| // Battle music | |
| this.load.audio("battle_music", "assets/audio/battle.mp3"); | |
| } | |
| create() { | |
| this.battleState = 'INIT'; | |
| this.sound.play('battle_music', { loop: true }); | |
| // 1. Setup Background and Enemy | |
| const backgroundKey = this.getBackgroundKey(this.terrain); | |
| this.add.image(400, 300, backgroundKey).setDisplaySize(800, 600); | |
| this.enemyConfig = this.getEnemyForTerrain(this.terrain); | |
| // 2. Initialize Player and Enemy Objects with Stats | |
| this.player = new BattleCharacter( | |
| this.playerData.name, | |
| this.playerData.maxHp, | |
| this.playerData.attack, | |
| this.playerData.magic, | |
| this.playerData.defense, | |
| this.playerData.atbRate | |
| ); | |
| this.enemy = new BattleEnemy(this.enemyConfig); | |
| // 3. Setup Sprites | |
| this.enemySprite = this.add.image(600, 330, this.enemy.key).setDisplaySize(175, 175); | |
| this.warriorSprite = this.add.sprite(200, 480, 'warrior_basic_attack').setDisplaySize(175, 175); | |
| this.warriorSprite.setOrigin(0.5, 1); | |
| this.warriorSprite.setDepth(2); | |
| // 4. Setup UI (HP Bars and Name Tags) | |
| this.setupUI(); | |
| // 5. Start Battle | |
| this.input.off('pointerdown'); // Remove original pointerdown listener | |
| this.battleState = 'WAITING_FOR_ATB'; | |
| console.log("Battle started! State: WAITING_FOR_ATB"); | |
| } | |
| update(time, delta) { | |
| if (this.battleState === 'WAITING_FOR_ATB') { | |
| const deltaFactor = delta / 1000; // Convert ms to seconds | |
| // Fill Player ATB | |
| this.player.atbValue = Math.min(100, this.player.atbValue + this.player.atbRate * deltaFactor * 10); | |
| this.updateHpBar('player', this.player.currentHp, this.player.maxHp); | |
| this.updateAtbBar('player', this.player.atbValue); | |
| if (this.player.atbValue >= 100) { | |
| this.player.atbValue = 100; | |
| this.battleState = 'PLAYER_TURN'; | |
| this.handlePlayerTurn(); | |
| return; | |
| } | |
| // Fill Enemy ATB | |
| this.enemy.atbValue = Math.min(100, this.enemy.atbValue + this.enemy.atbRate * deltaFactor * 10); | |
| this.updateHpBar('enemy', this.enemy.currentHp, this.enemy.maxHp); | |
| this.updateAtbBar('enemy', this.enemy.atbValue); | |
| if (this.enemy.atbValue >= 100) { | |
| this.enemy.atbValue = 100; | |
| this.battleState = 'ENEMY_TURN'; | |
| this.handleEnemyTurn(); | |
| return; | |
| } | |
| } | |
| } | |
| setupUI() { | |
| // Clear previous UI elements | |
| this.uiElements.forEach(el => el.destroy()); | |
| this.uiElements = []; | |
| const barHeight = 10; | |
| // --- Player Status Panel (Bottom Left) --- | |
| const pX = 10; | |
| const pY = 520; | |
| this.add.rectangle(pX, pY - 20, 200, 70, 0x000000, 0.7).setOrigin(0, 0).setStrokeStyle(2, 0xffffff); | |
| // Player Name | |
| this.uiElements.push(this.add.text(pX + 10, pY - 10, this.player.name, { fontSize: '14px', fill: '#fff' })); | |
| // Player HP Bar | |
| this.uiElements.push(this.add.text(pX + 10, pY + 10, 'HP:', { fontSize: '12px', fill: '#fff' })); | |
| this.playerHpBar = this.createBar(pX + 40, pY + 15, 150, barHeight, 0x880000, 0x00ff00, 100); | |
| this.uiElements.push(this.playerHpBar.barOutline, this.playerHpBar.barFill); | |
| // Player ATB Bar | |
| this.uiElements.push(this.add.text(pX + 10, pY + 30, 'ATB:', { fontSize: '12px', fill: '#fff' })); | |
| this.playerAtbBar = this.createBar(pX + 40, pY + 35, 150, barHeight, 0x888888, 0x3366ff, 0); | |
| this.uiElements.push(this.playerAtbBar.barOutline, this.playerAtbBar.barFill); | |
| // --- Enemy Status Panel (Top Right) --- | |
| const eX = 590; | |
| const eY = 10; | |
| this.add.rectangle(eX, eY, 200, 70, 0x000000, 0.7).setOrigin(0, 0).setStrokeStyle(2, 0xffffff); | |
| // Enemy Name | |
| this.uiElements.push(this.add.text(eX + 10, eY + 5, this.enemy.name, { fontSize: '14px', fill: '#fff' })); | |
| // Enemy HP Bar | |
| this.uiElements.push(this.add.text(eX + 10, eY + 25, 'HP:', { fontSize: '12px', fill: '#fff' })); | |
| this.enemyHpBar = this.createBar(eX + 40, eY + 30, 150, barHeight, 0x880000, 0x00ff00, 100); | |
| this.uiElements.push(this.enemyHpBar.barOutline, this.enemyHpBar.barFill); | |
| // Enemy ATB Bar | |
| this.uiElements.push(this.add.text(eX + 10, eY + 45, 'ATB:', { fontSize: '12px', fill: '#fff' })); | |
| this.enemyAtbBar = this.createBar(eX + 40, eY + 50, 150, barHeight, 0x888888, 0x3366ff, 0); | |
| this.uiElements.push(this.enemyAtbBar.barOutline, this.enemyAtbBar.barFill); | |
| this.updateHpBar('player', this.player.currentHp, this.player.maxHp); | |
| this.updateHpBar('enemy', this.enemy.currentHp, this.enemy.maxHp); | |
| } | |
| createBar(x, y, width, height, bgColor, fillColor, percentage) { | |
| const barOutline = this.add.rectangle(x, y, width + 2, height + 2, 0x000000).setOrigin(0, 0); | |
| const barFill = this.add.rectangle(x + 1, y + 1, width * (percentage / 100), height, fillColor).setOrigin(0, 0); | |
| barFill.fillColor = fillColor; | |
| barFill.maxWidth = width; | |
| return { barOutline, barFill }; | |
| } | |
| updateHpBar(target, currentHp, maxHp) { | |
| const percentage = Math.max(0, currentHp / maxHp); | |
| const bar = target === 'player' ? this.playerHpBar.barFill : this.enemyHpBar.barFill; | |
| bar.width = bar.maxWidth * percentage; | |
| bar.fillColor = percentage > 0.5 ? 0x00ff00 : percentage > 0.2 ? 0xffff00 : 0xff0000; | |
| bar.fillColor = Phaser.Display.Color.GetColor( | |
| (bar.fillColor >> 16) & 0xFF, | |
| (bar.fillColor >> 8) & 0xFF, | |
| bar.fillColor & 0xFF | |
| ); | |
| } | |
| updateAtbBar(target, atbValue) { | |
| const percentage = atbValue / 100; | |
| const bar = target === 'player' ? this.playerAtbBar.barFill : this.enemyAtbBar.barFill; | |
| bar.width = bar.maxWidth * percentage; | |
| } | |
| // --- Turn Management and Menu --- | |
| handlePlayerTurn() { | |
| this.player.isDefending = false; | |
| this.showActionMenu(); | |
| console.log("Player's Turn! Choose an action."); | |
| } | |
| handleEnemyTurn() { | |
| this.enemy.isDefending = false; | |
| this.player.atbValue = 0; // Enemy consumed its turn | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| // Simple Enemy AI: always attack | |
| this.time.delayedCall(1500, () => { | |
| this.performEnemyAttack(); | |
| }); | |
| } | |
| showActionMenu() { | |
| const menuX = 600; | |
| const menuY = 400; | |
| // Menu background | |
| this.actionMenuBg = this.add.rectangle(menuX - 5, menuY - 5, 180, 200, 0x000000, 0.9).setOrigin(0, 0).setStrokeStyle(2, 0xffffff); | |
| this.uiElements.push(this.actionMenuBg); | |
| const actions = [ | |
| { text: 'Attack', handler: this.performAttack }, | |
| { text: 'Magic', handler: this.performMagic }, | |
| { text: 'Ability', handler: this.performAbility }, | |
| { text: 'Defend', handler: this.performDefend }, | |
| { text: 'Item', handler: this.useItem }, | |
| { text: 'Flee', handler: this.performFlee }, | |
| ]; | |
| actions.forEach((action, index) => { | |
| const btn = this.add.text(menuX, menuY + index * 30, action.text, { fontSize: '18px', fill: '#fff' }) | |
| .setOrigin(0, 0) | |
| .setInteractive({ useHandCursor: true }) | |
| .on('pointerdown', () => { | |
| action.handler.call(this); // Call the handler within the scene context | |
| }) | |
| .on('pointerover', () => { | |
| btn.setFill('#ffcc00'); | |
| }) | |
| .on('pointerout', () => { | |
| btn.setFill('#fff'); | |
| }); | |
| this.uiElements.push(btn); | |
| }); | |
| } | |
| hideActionMenu() { | |
| this.uiElements = this.uiElements.filter(el => { | |
| if (el === this.actionMenuBg || this.actionMenuBg?.parentContainer?.list.includes(el)) { | |
| el.destroy(); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| this.actionMenuBg?.destroy(); | |
| this.actionMenuBg = null; | |
| } | |
| // --- Action Handlers --- | |
| performAttack() { | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| // Save warrior position before moving to attack | |
| const originalX = this.warriorSprite.x; | |
| const originalY = this.warriorSprite.y; | |
| // Target position (Enemy) | |
| const targetX = this.enemySprite.x - 70; | |
| const targetY = this.enemySprite.y + 50; | |
| // Move to the enemy and reduce size of the warrior | |
| this.tweens.add({ | |
| targets: this.warriorSprite, | |
| x: targetX, | |
| y: targetY, | |
| displayWidth: 175 * 0.7, | |
| displayHeight: 175 * 0.7, | |
| duration: 200, | |
| onComplete: () => { | |
| // Attack animation sequence | |
| this.warriorSprite.setTexture('warrior_basic_attack_2'); | |
| this.time.delayedCall(25, () => { | |
| this.warriorSprite.setTexture('warrior_basic_attack_3'); | |
| this.sound.play('warrior_slash', { volume: 0.8 }); | |
| this.time.delayedCall(20, () => { | |
| this.warriorSprite.setTexture('warrior_basic_attack_4'); | |
| // Apply Damage | |
| const damage = Math.max(1, this.player.attack - (this.enemy.defense / 2)); | |
| this.takeDamage('enemy', damage); | |
| this.showDamageText(this.enemySprite.x, this.enemySprite.y - 100, damage, '#ff0000'); | |
| this.cameraShake(50, 0.005); // Minor shake on hit | |
| this.time.delayedCall(60, () => { | |
| // Go back to default animation | |
| this.warriorSprite.setTexture('warrior_basic_attack'); | |
| // Restaurate warrior to default position | |
| this.tweens.add({ | |
| targets: this.warriorSprite, | |
| x: originalX, | |
| y: originalY, | |
| displayWidth: 175, | |
| displayHeight: 175, | |
| duration: 200, | |
| onComplete: () => { | |
| this.player.atbValue = 0; // Reset ATB | |
| this.checkBattleEnd(); | |
| } | |
| }); | |
| }); | |
| }); | |
| }); | |
| } | |
| }); | |
| } | |
| performEnemyAttack() { | |
| // Enemy attack animation and damage | |
| this.tweens.add({ | |
| targets: this.enemySprite, | |
| x: this.warriorSprite.x + 70, | |
| duration: 300, | |
| yoyo: true, | |
| ease: 'Sine.easeInOut', | |
| onYoyo: () => { | |
| // Apply Damage | |
| const damage = Math.max(1, this.enemy.attack - (this.player.defense / 2)); | |
| this.takeDamage('player', damage); | |
| this.showDamageText(this.warriorSprite.x, this.warriorSprite.y - 200, damage, '#ff0000'); | |
| this.sound.play('warrior_slash', { volume: 0.5 }); | |
| }, | |
| onComplete: () => { | |
| this.enemy.atbValue = 0; // Reset ATB | |
| this.checkBattleEnd(); | |
| } | |
| }); | |
| } | |
| performMagic() { | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| this.showStatusText(`${this.player.name} casts Fire! (Placeholder)`, 1500, () => { | |
| const damage = Math.max(1, this.player.magic + 10 - (this.enemy.defense / 4)); | |
| this.takeDamage('enemy', damage); | |
| this.showDamageText(this.enemySprite.x, this.enemySprite.y - 100, damage, '#00ffff'); | |
| this.player.atbValue = 0; | |
| this.checkBattleEnd(); | |
| }); | |
| } | |
| performAbility() { | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| this.showStatusText(`${this.player.name} uses Cleave! (Placeholder)`, 1500, () => { | |
| const damage = Math.max(1, this.player.attack * 1.5 - this.enemy.defense); | |
| this.takeDamage('enemy', damage); | |
| this.showDamageText(this.enemySprite.x, this.enemySprite.y - 100, damage, '#ff9900'); | |
| this.player.atbValue = 0; | |
| this.checkBattleEnd(); | |
| }); | |
| } | |
| performDefend() { | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| this.player.isDefending = true; | |
| this.showStatusText(`${this.player.name} is defending!`, 1500, () => { | |
| this.player.atbValue = 0; | |
| this.checkBattleEnd(); | |
| }); | |
| } | |
| useItem() { | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| this.showStatusText(`${this.player.name} uses Potion! (Placeholder)`, 1500, () => { | |
| const heal = 50; | |
| this.player.currentHp = Math.min(this.player.maxHp, this.player.currentHp + heal); | |
| this.updateHpBar('player', this.player.currentHp, this.player.maxHp); | |
| this.showDamageText(this.warriorSprite.x, this.warriorSprite.y - 200, heal, '#00ff00', 'Heal'); | |
| this.player.atbValue = 0; | |
| this.checkBattleEnd(); | |
| }); | |
| } | |
| performFlee() { | |
| this.hideActionMenu(); | |
| this.battleState = 'PROCESSING_ACTION'; | |
| const success = Math.random() > 0.4; // 60% chance to flee | |
| const fleeMessage = success ? 'Flee successful!' : 'Flee failed!'; | |
| this.showStatusText(fleeMessage, 2000, () => { | |
| if (success) { | |
| this.endBattle(false); // End battle, no victory/rewards | |
| } else { | |
| this.player.atbValue = 0; | |
| this.checkBattleEnd(); // Go back to waiting for ATB | |
| } | |
| }); | |
| } | |
| // --- Utility Functions --- | |
| takeDamage(target, damage) { | |
| const combatant = target === 'player' ? this.player : this.enemy; | |
| combatant.currentHp = Math.max(0, combatant.currentHp - damage); | |
| if (target === 'player') { | |
| this.updateHpBar('player', this.player.currentHp, this.player.maxHp); | |
| } else { | |
| this.updateHpBar('enemy', this.enemy.currentHp, this.enemy.maxHp); | |
| if (this.enemy.currentHp <= 0) { | |
| this.enemySprite.setTint(0x555555); | |
| } | |
| } | |
| } | |
| showDamageText(x, y, amount, color, prefix = '') { | |
| const text = this.add.text(x, y, `${prefix}${amount}`, { | |
| fontSize: '30px', | |
| fill: color, | |
| stroke: '#000000', | |
| strokeThickness: 4, | |
| fontFamily: 'Arial Black' | |
| }).setOrigin(0.5); | |
| this.tweens.add({ | |
| targets: text, | |
| y: y - 50, | |
| alpha: 0, | |
| duration: 1000, | |
| onComplete: () => { | |
| text.destroy(); | |
| } | |
| }); | |
| } | |
| showStatusText(message, duration, onComplete) { | |
| const statusText = this.add.text(400, 550, message, { | |
| fontSize: '24px', | |
| fill: '#fff', | |
| backgroundColor: '#333', | |
| padding: { x: 10, y: 5 } | |
| }).setOrigin(0.5); | |
| this.uiElements.push(statusText); | |
| this.time.delayedCall(duration, () => { | |
| statusText.destroy(); | |
| this.uiElements = this.uiElements.filter(el => el !== statusText); | |
| if (onComplete) onComplete(); | |
| }); | |
| } | |
| cameraShake(duration, intensity) { | |
| this.cameras.main.shake(duration, intensity); | |
| } | |
| checkBattleEnd() { | |
| if (this.player.currentHp <= 0) { | |
| this.endBattle(false); | |
| } else if (this.enemy.currentHp <= 0) { | |
| this.endBattle(true); | |
| } else { | |
| // Continue the battle loop | |
| this.battleState = 'WAITING_FOR_ATB'; | |
| } | |
| } | |
| endBattle(victory) { | |
| if (this.battleState === 'BATTLE_END') return; | |
| this.battleState = 'BATTLE_END'; | |
| this.sound.stopAll(); | |
| const resultMessage = victory ? 'VICTORY!' : 'DEFEAT...'; | |
| this.showStatusText(resultMessage, 3000, () => { | |
| this.scene.stop(); | |
| if (this.onBattleEnd) { | |
| this.onBattleEnd({ | |
| victory: victory, | |
| experience: victory ? 100 : 0, | |
| gold: victory ? 50 : 0, | |
| enemy: this.enemy.name | |
| }); | |
| } | |
| }); | |
| } | |
| // --- Configuration from original code --- | |
| getBackgroundKey(terrain) { | |
| const terrainMap = { | |
| 'grass': 'battle_grass', | |
| 'forest': 'battle_forest', | |
| 'mountain': 'battle_mountain', | |
| 'path': 'battle_dirt', | |
| 'water': 'battle_grass' | |
| }; | |
| return terrainMap[terrain] || 'battle_grass'; | |
| } | |
| // select enemies based on terrain + chance of finding the enemy in that terrain | |
| getEnemyForTerrain(terrain) { | |
| const baseEnemyStats = { | |
| // HP, ATK, MAG, DEF, ATB Rate | |
| 'goat': { hp: 80, attack: 10, magic: 5, defense: 5, atbRate: 4 }, | |
| 'war_dwarf': { hp: 150, attack: 25, magic: 5, defense: 15, atbRate: 3 }, | |
| 'snake': { hp: 100, attack: 15, magic: 10, defense: 8, atbRate: 6 }, | |
| 'wolf': { hp: 120, attack: 20, magic: 5, defense: 10, atbRate: 5 }, | |
| 'fly': { hp: 50, attack: 8, magic: 2, defense: 2, atbRate: 7 }, | |
| 'war_blob': { hp: 250, attack: 15, magic: 5, defense: 20, atbRate: 2 }, | |
| 'war_orc': { hp: 180, attack: 30, magic: 10, defense: 12, atbRate: 4 }, | |
| 'magic_demon': { hp: 130, attack: 20, magic: 35, defense: 8, atbRate: 5 }, | |
| }; | |
| const enemyPools = { | |
| 'mountain': [ | |
| { key: 'goat', name: 'Mountain Goat', weight: 35 }, | |
| { key: 'war_dwarf', name: 'War Dwarf', weight: 30 }, | |
| { key: 'war_blob', name: 'War Blob', weight: 20 }, | |
| { key: 'magic_demon', name: 'Magic Demon', weight: 5 }, | |
| { key: 'wolf', name: 'Wolf', weight: 10 } | |
| ], | |
| 'forest': [ | |
| { key: 'snake', name: 'Forest Snake', weight: 40 }, | |
| { key: 'wolf', name: 'Wolf', weight: 35 }, | |
| { key: 'fly', name: 'Giant Fly', weight: 15 }, | |
| { key: 'magic_demon', name: 'Magic Demon', weight: 5 }, | |
| { key: 'war_orc', name: 'Forest Orc', weight: 5 } | |
| ], | |
| 'grass': [ | |
| { key: 'fly', name: 'Giant Fly', weight: 40 }, | |
| { key: 'wolf', name: 'Wolf', weight: 30 }, | |
| { key: 'snake', name: 'Snake', weight: 15 }, | |
| { key: 'magic_demon', name: 'Magic Demon', weight: 5 }, | |
| { key: 'goat', name: 'Goat', weight: 10 } | |
| ], | |
| 'path': [ | |
| { key: 'war_orc', name: 'Orc Warrior', weight: 40 }, | |
| { key: 'fly', name: 'Giant Fly', weight: 25 }, | |
| { key: 'wolf', name: 'Wolf', weight: 20 }, | |
| { key: 'magic_demon', name: 'Magic Demon', weight: 5 }, | |
| { key: 'snake', name: 'Snake', weight: 10 } | |
| ], | |
| 'water': [ // Not implemented yet, walking on water not unlocked | |
| { key: 'fly', name: 'Giant Fly', weight: 40 }, | |
| { key: 'snake', name: 'Water Snake', weight: 30 }, | |
| { key: 'magic_demon', name: 'Magic Demon', weight: 10 }, | |
| { key: 'wolf', name: 'Wolf', weight: 20 } | |
| ] | |
| }; | |
| const pool = enemyPools[terrain] || enemyPools['grass']; | |
| const selectedEnemy = this.selectRandomEnemy(pool); | |
| // Merge base stats with selection | |
| return { | |
| ...selectedEnemy, | |
| ...baseEnemyStats[selectedEnemy.key] | |
| }; | |
| } | |
| // Select the enemy based on % | |
| selectRandomEnemy(enemyPool) { | |
| // Calculate total weight | |
| const totalWeight = enemyPool.reduce((sum, enemy) => sum + enemy.weight, 0); | |
| let random = Math.random() * totalWeight; | |
| let weightSum = 0; | |
| for (const enemy of enemyPool) { | |
| weightSum += enemy.weight; | |
| if (random <= weightSum) { | |
| return enemy; | |
| } | |
| } | |
| // Fallback: first enemy available | |
| return enemyPool[0]; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment