User prompt
1.2
User prompt
1.3
User prompt
1.1
User prompt
podemos solucionar el problema del mana?
User prompt
quiero implementar un sistema que cuando se toquen las cartas lanzen el hechizo que tiene configurado como lo puedo implementar por pasos
User prompt
quiero que las cartas lanzen hechizos cuando sean tocadas
User prompt
las cartas tiene muchas animaciones elimina sus animaciones
User prompt
elimina sub menus complejos
User prompt
simplifica el gamemenu
User prompt
procede con la simplificacion del tutorial
User prompt
refactorizar tweenmanager ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
simplificar sistemas de hechizos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
simplifica el game menu ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
si empieza con la consolidacion de clases similares ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween(readyAura, {' Line Number: 5950 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: '_typeof3 is not defined' in or related to this line: 'console.log('tween plugin available:', _typeof3(tween));' Line Number: 4925 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'setInterval is not a function' in or related to this line: 'setInterval(function () {' Line Number: 4154 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
centraliza el sistema de tween ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 5.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 4.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 4.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 3.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 3.1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementa el 2.2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
implementala ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.bobOffset = Math.random() * Math.PI * 2;
self.initialY = 0;
self.update = function () {
if (self.initialY === 0) {
self.initialY = self.y;
}
// Only do bobbing animation and collection if not animating to coin counter
if (!self.isAnimating) {
// Bobbing animation
self.y = self.initialY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 10;
// Check collection by wizard
if (wizard && self.intersects(wizard)) {
self.collect();
}
}
};
self.collect = function () {
LK.getSound('coinCollect').play();
LK.setScore(LK.getScore() + 5);
coinCounter++;
coinText.setText('Coins: ' + coinCounter);
// Remove from coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
// Destroy coin directly without object pool
self.destroy();
};
return self;
});
// Unified Enemy class that handles all enemy types
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
// Set enemy type and configure based on it
self.enemyType = type || 'skeleton';
self.currentFrame = 1;
self.animationTimer = 0;
self.animationState = 'walking';
self.frozen = false;
self.frozenTimer = 0;
self.isDying = false;
self.lastX = 0;
self.speedTweenStarted = false;
self.lastIntersecting = false;
// Configure enemy based on type
var config = {
skeleton: {
health: 100,
speed: 3,
damage: 20,
animationSpeed: 15,
assetPrefix: 'esqueleto',
scale: 3.0,
vibration: [100],
tint: null
},
ogre: {
health: 200,
speed: 2.5,
damage: 30,
animationSpeed: 18,
assetPrefix: 'ogre',
scale: 3.5,
vibration: [200],
tint: null
},
knight: {
health: 300,
speed: 2,
damage: 40,
animationSpeed: 20,
assetPrefix: 'knight',
scale: 3.0,
vibration: [150],
tint: null
},
miniBoss: {
health: 3000,
speed: 4,
damage: 75,
animationSpeed: 12,
assetPrefix: 'ogre',
scale: 5.0,
vibration: [300, 100, 300],
tint: 0x8B0000
}
};
var enemyConfig = config[self.enemyType] || config.skeleton;
self.health = enemyConfig.health;
self.maxHealth = self.health;
self.speed = enemyConfig.speed;
self.damage = enemyConfig.damage;
self.animationSpeed = enemyConfig.animationSpeed;
// Create animation frames
self.animationFrames = [];
for (var i = 1; i <= 4; i++) {
var frameGraphics = self.attachAsset(enemyConfig.assetPrefix + i, {
anchorX: 0.5,
anchorY: 1.0,
scaleX: enemyConfig.scale,
scaleY: enemyConfig.scale
});
frameGraphics.visible = i === 1;
if (enemyConfig.tint) {
frameGraphics.tint = enemyConfig.tint;
}
self.animationFrames.push(frameGraphics);
}
// Create health bar for mini boss
if (self.enemyType === 'miniBoss') {
self.healthBarBg = game.addChild(LK.getAsset('miniBossHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 300,
scaleX: 8,
scaleY: 2
}));
self.healthBarFg = game.addChild(LK.getAsset('miniBossHealthBar', {
anchorX: 0.0,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 300,
scaleX: 8,
scaleY: 2
}));
self.healthText = new Text2('Boss Health: ' + self.health + '/' + self.maxHealth, {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
self.healthText.anchor.set(0.5, 0.5);
self.healthText.x = 2048 / 2;
self.healthText.y = 250;
game.addChild(self.healthText);
}
// Update health bar for mini boss
self.updateHealthBar = function () {
if (self.enemyType !== 'miniBoss' || !self.healthBarFg) return;
var healthPercent = self.health / self.maxHealth;
self.healthBarFg.scaleX = healthPercent * 8;
self.healthText.setText('Boss Health: ' + self.health + '/' + self.maxHealth);
if (healthPercent > 0.6) {
self.healthBarFg.tint = 0xff0000;
} else if (healthPercent > 0.3) {
self.healthBarFg.tint = 0xff4500;
} else {
self.healthBarFg.tint = 0x8B0000;
}
};
// Optimized animation system
self.updateAnimation = function () {
self.animationTimer++;
var frameSpeed = self.animationSpeed;
if (self.animationState === 'attacking') frameSpeed = Math.floor(frameSpeed * 0.5);else if (self.animationState === 'dying') frameSpeed = Math.floor(frameSpeed * 1.3);else if (self.animationState === 'idle') frameSpeed = Math.floor(frameSpeed * 1.7);
if (self.animationTimer >= frameSpeed) {
self.animationTimer = 0;
self.animationFrames[self.currentFrame - 1].visible = false;
if (self.animationState === 'walking') {
self.currentFrame++;
if (self.currentFrame > 4) self.currentFrame = 1;
} else if (self.animationState === 'attacking') {
self.currentFrame++;
if (self.currentFrame > 4) self.currentFrame = 2;
} else if (self.animationState === 'dying') {
if (self.currentFrame < 4) self.currentFrame++;
} else if (self.animationState === 'idle') {
self.currentFrame = self.currentFrame === 1 ? 2 : 1;
}
self.animationFrames[self.currentFrame - 1].visible = true;
}
};
self.update = function () {
if (tutorial && tutorial.isActive || self.isDying) return;
// Animation and speed progression
self.updateAnimation();
if (!self.speedTweenStarted) {
self.speedTweenStarted = true;
tween(self, {
speed: self.speed * 1.5
}, {
duration: 10000,
easing: tween.easeOut
});
}
// Handle frozen state
if (self.frozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) self.frozen = false;
return;
}
// Enhanced movement toward wizard
if (wizard) {
var dx = wizard.x - self.x;
var dy = wizard.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var speedMult = self.timeSlowed ? self.timeSlowAmount : 1.0;
self.x += dx / distance * self.speed * speedMult;
self.y += dy / distance * self.speed * speedMult;
var flipScale = dx < 0 ? -enemyConfig.scale : enemyConfig.scale;
for (var frameIdx = 0; frameIdx < self.animationFrames.length; frameIdx++) {
self.animationFrames[frameIdx].scaleX = flipScale;
}
} else {
self.y += self.speed;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.animationState = 'attacking';
// Create damage text
var damageText = new Text2('-' + damage, {
size: 120,
fill: 0xFF4444,
font: "monospace"
});
damageText.anchor.set(0.5, 0.5);
damageText.x = self.x + (Math.random() - 0.5) * 60;
damageText.y = self.y - 40;
game.addChild(damageText);
var startY = damageText.y;
tween(damageText, {
y: startY - 120,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
damageText.destroy();
}
});
// Flash effect
LK.effects.flashObject(self, 0xFF0000, 300);
// Return to walking state after brief animation
tween({}, {}, {
duration: 300,
onFinish: function onFinish() {
if (self.animationState === 'attacking') self.animationState = 'walking';
}
});
if (self.enemyType === 'miniBoss') {
self.updateHealthBar();
}
if (self.health <= 0) self.die();
};
self.down = function (x, y, obj) {
if (tutorial && tutorial.isActive || self.isDying) return;
if (wizard && wizard.attackCooldown > 0) {
LK.effects.flashObject(self, 0xFF0000, 200);
return;
}
// Vibration feedback
if (typeof LK.vibrate === 'function') {
LK.vibrate(enemyConfig.vibration);
}
selectedEnemy = self;
LK.effects.flashObject(self, 0xFFFF00, 500);
if (wizard && projectiles.length < 10) {
var projectile = ProjectileFactory.createBasicAttack(wizard, self);
projectile.targetEnemy = self;
projectiles.push(projectile);
LK.getSound('spellCast').play();
wizard.attackCooldown = 30;
}
};
self.die = function () {
if (globalDeathHandler) {
// Get appropriate array based on type
var enemyArray = enemies;
if (self.enemyType === 'ogre') enemyArray = ogres;else if (self.enemyType === 'knight') enemyArray = knights;else if (self.enemyType === 'miniBoss') enemyArray = miniBosses;
globalDeathHandler.executeEnemyDeath(self, enemyArray);
}
};
return self;
});
// Unified EnemyManager for streamlined enemy lifecycle management
var EnemyManager = Container.expand(function () {
var self = Container.call(this);
// Use global arrays directly - no separate collections needed
// Unified enemy configuration templates
self.enemyTemplates = {
skeleton: {
baseHealth: 100,
baseSpeed: 3,
damage: 20,
coinReward: 1
},
ogre: {
baseHealth: 200,
baseSpeed: 2.5,
damage: 30,
coinReward: 1
},
knight: {
baseHealth: 300,
baseSpeed: 2,
damage: 40,
coinReward: 1
},
miniBoss: {
baseHealth: 3000,
baseSpeed: 4,
damage: 75,
coinReward: 5
}
};
// Streamlined enemy creation with unified Enemy class
self.createEnemy = function (type, difficulty, level, pathOverride) {
var template = self.enemyTemplates[type];
if (!template) return null;
// Create enemy using unified Enemy class
var enemy = new Enemy(type);
// Apply difficulty scaling
enemy.speed *= 1 + level * 0.3;
enemy.pathIndex = pathOverride || self.selectOptimalPath(type);
self.positionEnemy(enemy, type);
self.applyDifficultyModifications(enemy, type, difficulty);
return enemy;
};
// Unified enemy positioning system
self.positionEnemy = function (enemy, type) {
// Use direct spawn position values
var spawnPositions = [{
x: 2048 / 2,
y: -100
}, {
x: 2048 + 50,
y: -50
}, {
x: -50,
y: -50
}, {
x: -100,
y: 2732 / 2 + 400
}, {
x: 2048 + 100,
y: 2732 / 2 + 400
}];
var spawnPos = spawnPositions[enemy.pathIndex];
if (spawnPos) {
enemy.x = Math.max(50, Math.min(1998, spawnPos.x));
enemy.y = type === 'miniBoss' ? -200 : Math.max(-200, Math.min(2732 + 100, spawnPos.y));
enemy.lastX = enemy.x;
}
};
// Streamlined difficulty modifications
self.applyDifficultyModifications = function (enemy, type, difficulty) {
if (type === 'miniBoss') {
enemy.updateHealthBar();
LK.effects.flashScreen(0x8B0000, 1000);
} else if (type === 'skeleton' && Math.random() < 0.3) {
LK.getSound('enemyGrowl').play();
}
// Elite enemy system for hard difficulty
if (difficulty === 'DIFICIL' && enemyKillCounter >= 20 && Math.random() < 0.15) {
self.createEliteEnemy(enemy, type);
}
};
// Unified elite enemy creation
self.createEliteEnemy = function (enemy, type) {
var eliteMultipliers = {
health: 1.8,
speed: 1.3,
color: 0xFF6600
};
if (type === 'ogre') eliteMultipliers.color = 0xFF0000;
if (type === 'knight') eliteMultipliers.color = 0xFFD700;
if (type === 'miniBoss') eliteMultipliers.color = 0x8B0000;
enemy.health *= eliteMultipliers.health;
enemy.speed *= eliteMultipliers.speed;
enemy.maxHealth = enemy.health;
enemy.isElite = true;
for (var i = 0; i < enemy.animationFrames.length; i++) {
enemy.animationFrames[i].tint = eliteMultipliers.color;
}
};
// Optimized path selection system
self.selectOptimalPath = function (type) {
if (type === 'skeleton' && enemyKillCounter < 5) return 0;
if (type === 'miniBoss') return 0;
var available = [];
for (var i = 0; i < 5; i++) {
if (pathConsecutiveSpawns[i] < 2) available.push(i);
}
if (available.length === 0) {
for (var i = 0; i < 5; i++) pathConsecutiveSpawns[i] = 0;
available = [0, 1, 2, 3, 4];
}
return available[Math.floor(Math.random() * available.length)];
};
// Unified enemy collection management
self.addToCollection = function (enemy, type) {
if (type === 'skeleton') enemies.push(enemy);else if (type === 'ogre') ogres.push(enemy);else if (type === 'knight') knights.push(enemy);else if (type === 'miniBoss') miniBosses.push(enemy);
};
// Streamlined enemy removal system
self.removeFromCollection = function (enemy, type) {
var collection = [];
if (type === 'skeleton') collection = enemies;else if (type === 'ogre') collection = ogres;else if (type === 'knight') collection = knights;else if (type === 'miniBoss') collection = miniBosses;
if (collection.length > 0) {
for (var i = collection.length - 1; i >= 0; i--) {
if (collection[i] === enemy) {
collection.splice(i, 1);
break;
}
}
}
};
// Get all enemies as unified array
self.getAllEnemies = function () {
var allEnemies = [];
allEnemies = allEnemies.concat(enemies);
allEnemies = allEnemies.concat(ogres);
allEnemies = allEnemies.concat(knights);
allEnemies = allEnemies.concat(miniBosses);
return allEnemies;
};
return self;
});
var EnergyOrb = Container.expand(function () {
var self = Container.call(this);
// Create energy sphere visual
var sphereGraphics = self.attachAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Add glowing effect
sphereGraphics.alpha = 0.8;
self.attackTimer = 0;
self.attackInterval = 180; // 3 seconds at 60fps
self.orbitalAngle = 0;
self.orbitalRadius = 120;
self.update = function () {
// Pause energy orb when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Keep sphere at wizard's position (stationary relative to wizard)
if (wizard) {
self.x = wizard.x + 140; // Position further to the right side of wizard
self.y = wizard.y - 20; // Position slightly lower relative to wizard
}
// Pulsing glow effect
var pulse = 1 + Math.sin(LK.ticks * 0.2) * 0.3;
sphereGraphics.scaleX = 1.5 * pulse;
sphereGraphics.scaleY = 1.5 * pulse;
// Attack timer - keep original interval regardless of upgrades
self.attackTimer++;
if (self.attackTimer >= 180) {
// Fixed at 3 seconds
self.attackTimer = 0;
self.attackClosestEnemy();
}
};
self.attackClosestEnemy = function () {
var closestEnemy = null;
var closestDistance = Infinity;
// Check all enemy types for closest one
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
// Attack closest enemy if found
if (closestEnemy) {
// Create energy beam projectile using unified factory
var energyBeam = ProjectileFactory.createProjectile('energyBeam', self.x, self.y, closestEnemy.x, closestEnemy.y, {
targetEnemy: closestEnemy
});
// Flash effect on sphere when attacking
globalTween(sphereGraphics, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
globalTween(sphereGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.8
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.getSound('spellCast').play();
}
};
return self;
});
var GameMenu = Container.expand(function () {
var self = Container.call(this);
// Simple spell deck - load from storage or use defaults
var currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning'];
storage.spellDeck = currentDeck.slice();
// Menu background image instead of cave background
var menuBg = self.attachAsset('menuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 25.0,
scaleY: 35.0
});
menuBg.alpha = 1.0;
// Title text
var titleText = new Text2('WIZARD DEFENDER', {
size: 150,
fill: 0xFFD700,
font: "monospace"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 800;
self.addChild(titleText);
// Instructions text
var instructionsText = new Text2('TAP ENEMIES TO ATTACK\nDEFEND YOUR CASTLE!', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 2048 / 2;
instructionsText.y = 1200;
self.addChild(instructionsText);
// Start button
var startButton = self.attachAsset('wizard1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1500,
scaleX: 2,
scaleY: 2
});
var startButtonText = new Text2('START GAME', {
size: 100,
fill: 0x000000,
font: "monospace"
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.x = 2048 / 2;
startButtonText.y = 1600;
self.addChild(startButtonText);
// Configuration button
var configButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1800,
scaleX: 4,
scaleY: 2
});
configButton.tint = 0x4169E1;
var configButtonText = new Text2('CONFIGURACION', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
configButtonText.anchor.set(0.5, 0.5);
configButtonText.x = 2048 / 2;
configButtonText.y = 1800;
self.addChild(configButtonText);
// Shop button
var shopButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1950,
scaleX: 4,
scaleY: 2
});
shopButton.tint = 0xFF6B35;
var shopButtonText = new Text2('TIENDA', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
shopButtonText.anchor.set(0.5, 0.5);
shopButtonText.x = 2048 / 2;
shopButtonText.y = 1950;
self.addChild(shopButtonText);
// Deck button
var deckButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2150,
scaleX: 4,
scaleY: 2
});
deckButton.tint = 0x8A2BE2;
var deckButtonText = new Text2('DECK HECHIZOS', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
deckButtonText.anchor.set(0.5, 0.5);
deckButtonText.x = 2048 / 2;
deckButtonText.y = 2150;
self.addChild(deckButtonText);
// Tutorial button (only show if tutorial was completed before)
if (storage.tutorialCompleted) {
var tutorialButton = self.attachAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2350,
scaleX: 4,
scaleY: 2
});
tutorialButton.tint = 0x2E8B57;
var tutorialButtonText = new Text2('TUTORIAL', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
tutorialButtonText.anchor.set(0.5, 0.5);
tutorialButtonText.x = 2048 / 2;
tutorialButtonText.y = 2350;
self.addChild(tutorialButtonText);
}
// Simplified button interaction router
self.down = function (x, y, obj) {
if (self.configMode) {
self.handleConfigInteraction(x, y);
} else if (self.deckMode) {
self.handleDeckInteraction(x, y);
} else if (self.shopMode) {
self.handleShopInteraction(x, y);
} else {
self.handleMainMenuInteraction(x, y);
}
};
// Simplified configuration interaction handler
self.handleConfigInteraction = function (x, y) {
// Define interaction zones
var zones = [{
yMin: 1150,
yMax: 1250,
action: 'music'
}, {
yMin: 1350,
yMax: 1450,
action: 'sound'
}, {
yMin: 1550,
yMax: 1650,
action: 'difficulty'
}, {
yMin: 1950,
yMax: 2050,
action: 'back'
}];
for (var i = 0; i < zones.length; i++) {
var zone = zones[i];
if (y >= zone.yMin && y <= zone.yMax) {
self.handleConfigAction(zone.action);
return;
}
}
};
// Handle specific config actions
self.handleConfigAction = function (action) {
if (action === 'music') {
var vol = storage.musicVolume || 0.7;
vol = vol >= 1.0 ? 0.0 : Math.min(1.0, vol + 0.1);
storage.musicVolume = vol;
self.musicVolumeText.setText('VOLUMEN MUSICA: ' + Math.round(vol * 100) + '%');
} else if (action === 'sound') {
var vol = storage.soundVolume || 1.0;
vol = vol >= 1.0 ? 0.0 : Math.min(1.0, vol + 0.1);
storage.soundVolume = vol;
self.soundVolumeText.setText('VOLUMEN SONIDO: ' + Math.round(vol * 100) + '%');
} else if (action === 'difficulty') {
var difficulties = ['FACIL', 'NORMAL', 'DIFICIL'];
var current = storage.difficulty || 'NORMAL';
var index = (difficulties.indexOf(current) + 1) % difficulties.length;
storage.difficulty = difficulties[index];
self.difficultyText.setText('DIFICULTAD: ' + difficulties[index]);
} else if (action === 'back') {
self.hideConfigMenu();
}
};
// Simplified deck interaction handler
self.handleDeckInteraction = function (x, y) {
// Check back button first (fastest check)
if (y >= 2350 && y <= 2650) {
self.hideDeck();
return;
}
// Check deck cards
var clickedCard = self.findClickedCard(x, y, self.deckElements, true);
if (clickedCard) {
self.handleDeckCardClick(clickedCard, x, y);
return;
}
// Check available cards
var availableCard = self.findClickedCard(x, y, self.availableElements, false);
if (availableCard) {
self.handleAvailableCardClick(availableCard);
}
};
// Find clicked card in collection
self.findClickedCard = function (x, y, collection, isDeckCard) {
for (var i = 0; i < collection.length; i++) {
var element = collection[i];
if (element.spellId && element.isDeckCard === isDeckCard && self.isCardClicked(element, x, y)) {
return element;
}
}
return null;
};
// Helper to check if card was clicked
self.isCardClicked = function (element, x, y) {
var cardX = element.x;
var cardY = element.y;
return x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225;
};
// Handle deck card clicks (cast or remove)
self.handleDeckCardClick = function (element, x, y) {
// Visual feedback
LK.effects.flashObject(element, 0x00FF00, 200);
var cardY = element.y;
// Always cast spell when card is touched - no area distinction needed
self.attemptSpellCast(element);
};
// Handle available card clicks (add to deck or cast if already in deck)
self.handleAvailableCardClick = function (element) {
LK.effects.flashObject(element, 0x00FF00, 300);
// Check if spell is already in deck - if so, cast it
if (self.spellDeck.currentDeck.indexOf(element.spellId) !== -1) {
// Spell is in deck, cast it instead of adding
self.attemptSpellCast(element);
return;
}
// Spell not in deck, try to add it
if (self.spellDeck.addToDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0x00FF00, 200);
self.showMessage('HECHIZO AÑADIDO', 0x66FF66);
} else {
LK.effects.flashScreen(0xFF0000, 200);
self.showMessage('DECK LLENO (MAX 5)', 0xFF6666);
}
};
// Attempt to cast spell with validation
self.attemptSpellCast = function (element) {
var canCast = _canCastSpell(element.spellId);
if (canCast) {
self.executeSpellCast(element);
} else {
self.showSpellCastError(element);
}
};
// Execute successful spell cast
self.executeSpellCast = function (element) {
var success = castSpell(element.spellId);
if (success) {
self.showMessage('HECHIZO LANZADO!', 0x00FF00);
// Close deck menu after successful spell cast
setTimeout(function () {
if (self.deckMode) {
self.hideDeck();
}
}, 500); // Small delay to show the success message
} else {
self.showMessage('FALLO AL LANZAR HECHIZO', 0xFF4444);
}
};
// Show spell cast error with specific feedback
self.showSpellCastError = function (element) {
var spell = _getSpell(element.spellId);
if (spell && currentMana < spell.manaCost) {
self.showMessage('MANA INSUFICIENTE!\nRequiere: ' + spell.manaCost, 0xFF4444);
} else {
var currentTick = LK.ticks || 0;
if (cardCooldowns[element.spellId] && currentTick < cardCooldowns[element.spellId]) {
var timeRemaining = Math.ceil((cardCooldowns[element.spellId] - currentTick) / 60);
self.showMessage('EN RECARGA!\nEspera: ' + timeRemaining + ' segundos', 0x4169E1);
} else {
self.showMessage('NO SE PUEDE LANZAR', 0xFF6666);
}
}
LK.effects.flashObject(element, 0xFF0000, 200);
};
// Remove spell from deck
self.removeFromDeck = function (element) {
if (self.spellDeck.removeFromDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0xFF8800, 200);
self.showMessage('HECHIZO REMOVIDO', 0xFF6666);
} else {
LK.effects.flashScreen(0xFF0000, 200);
}
};
// Simplified shop interaction handler
self.handleShopInteraction = function (x, y) {
// Check back button
if (y >= 1950 && y <= 2050) {
self.hideShop();
return;
}
// Check purchase buttons
var buttonX = 2048 / 2 + 300;
if (x >= buttonX - 100 && x <= buttonX + 100) {
for (var i = 0; i < 3; i++) {
var itemY = 1100 + i * 200;
if (y >= itemY - 50 && y <= itemY + 50) {
self.purchaseShopItem(i);
return;
}
}
}
};
// Handle shop item purchase
self.purchaseShopItem = function (itemIndex) {
var shopItems = [{
name: 'POCION SALUD',
cost: 10
}, {
name: 'ESCUDO MAGICO',
cost: 15
}, {
name: 'ESPADA MALDITA',
cost: 20
}];
var item = shopItems[itemIndex];
if (coinCounter >= item.cost) {
coinCounter -= item.cost;
self.applyShopItemEffect(itemIndex);
LK.effects.flashScreen(0x00FF00, 300);
} else {
LK.effects.flashScreen(0xFF0000, 300);
}
};
// Apply shop item effects
self.applyShopItemEffect = function (itemIndex) {
if (itemIndex === 0 && wizard) {
// Health potion
wizard.health = Math.min(wizard.health + 50, wizard.maxHealth);
updateHealthBar();
} else if (itemIndex === 1 && wizard) {
// Magic shield
wizard.shieldActive = true;
wizard.maxShieldHits = 3;
wizard.currentShieldHits = 0;
} else if (itemIndex === 2 && wizard) {
// Cursed sword
wizard.tempDamageBoost = true;
wizard.tempDamageTimer = 1800;
}
};
// Simplified main menu interaction handler
self.handleMainMenuInteraction = function (x, y) {
var centerX = 2048 / 2;
var buttonWidth = 400;
// Define menu buttons with their zones
var buttons = [{
yMin: 1450,
yMax: 1650,
action: 'start',
needsX: false
}, {
yMin: 1700,
yMax: 1900,
action: 'config',
needsX: true
}, {
yMin: 1850,
yMax: 2050,
action: 'shop',
needsX: true
}, {
yMin: 2050,
yMax: 2250,
action: 'deck',
needsX: true
}, {
yMin: 2250,
yMax: 2450,
action: 'tutorial',
needsX: true
}];
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
if (y >= btn.yMin && y <= btn.yMax) {
if (!btn.needsX || x >= centerX - buttonWidth / 2 && x <= centerX + buttonWidth / 2) {
self.handleMenuAction(btn.action);
return;
}
}
}
};
// Handle specific menu actions
self.handleMenuAction = function (action) {
if (action === 'start') {
self.startGame();
} else if (action === 'config') {
self.showConfigMenu();
} else if (action === 'shop') {
self.showShop();
} else if (action === 'deck') {
self.showDeck();
} else if (action === 'tutorial' && storage.tutorialCompleted && tutorial) {
self.visible = false;
tutorial.startTutorial();
}
};
// Simplified message display
self.showMessage = function (text, color) {
var message = self.createMenuText(text, 2048 / 2, 2200, 50, color);
tween(message, {
alpha: 0,
y: message.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (message.parent) message.destroy();
}
});
};
self.showConfigMenu = function () {
if (!self.configOverlay) {
self.configOverlay = self.createMenuOverlay(0x000000);
self.configTitle = self.createMenuText('CONFIGURACION', 2048 / 2, 800, 120, 0xFFD700);
self.musicVolumeText = self.createMenuText('VOLUMEN MUSICA: ' + Math.round((storage.musicVolume || 0.7) * 100) + '%', 2048 / 2, 1200, 80, 0xFFFFFF);
self.soundVolumeText = self.createMenuText('VOLUMEN SONIDO: ' + Math.round((storage.soundVolume || 1.0) * 100) + '%', 2048 / 2, 1400, 80, 0xFFFFFF);
self.difficultyText = self.createMenuText('DIFICULTAD: ' + (storage.difficulty || 'NORMAL'), 2048 / 2, 1600, 80, 0xFFFFFF);
self.backButton = self.createMenuButton('coin', 2048 / 2, 2000, 0x00FF00);
self.backText = self.createMenuText('VOLVER', 2048 / 2, 2000, 80, 0xFFFFFF);
}
self.configOverlay.visible = true;
self.configMode = true;
};
self.hideConfigMenu = function () {
if (self.configOverlay) {
self.configOverlay.destroy();
self.configOverlay = null;
}
// Remove all configuration text elements
if (self.musicVolumeText) {
self.musicVolumeText.destroy();
self.musicVolumeText = null;
}
if (self.soundVolumeText) {
self.soundVolumeText.destroy();
self.soundVolumeText = null;
}
if (self.difficultyText) {
self.difficultyText.destroy();
self.difficultyText = null;
}
// Remove back button elements
if (self.backButton) {
self.backButton.destroy();
self.backButton = null;
}
if (self.backText) {
self.backText.destroy();
self.backText = null;
}
// Remove configuration title
if (self.configTitle) {
self.configTitle.destroy();
self.configTitle = null;
}
// Remove all configuration children that were added
for (var i = self.children.length - 1; i >= 0; i--) {
var child = self.children[i];
// Remove config-related elements (title, texts, buttons created in showConfigMenu)
if (child.setText && child.text && (child.text.includes('CONFIGURACION') || child.text.includes('VOLUMEN') || child.text.includes('DIFICULTAD') || child.text.includes('VOLVER'))) {
child.destroy();
}
}
self.configMode = false;
// Reset to show main menu elements
self.visible = true;
};
self.showShop = function () {
if (!self.shopOverlay) {
self.shopOverlay = self.createMenuOverlay(0x000033);
self.shopTitle = self.createMenuText('TIENDA', 2048 / 2, 800, 120, 0xFFD700);
var shopItems = self.getShopItemsData();
self.initializeShopArrays();
self.createShopItems(shopItems);
self.shopBackButton = self.createMenuButton('coin', 2048 / 2, 2000, 0x00FF00);
self.shopBackText = self.createMenuText('VOLVER', 2048 / 2, 2000, 80, 0xFFFFFF);
}
self.shopOverlay.visible = true;
self.shopMode = true;
};
self.hideShop = function () {
if (self.shopOverlay) {
self.shopOverlay.destroy();
self.shopOverlay = null;
}
// Remove shop title
if (self.shopTitle) {
self.shopTitle.destroy();
self.shopTitle = null;
}
// Remove shop back button elements
if (self.shopBackButton) {
self.shopBackButton.destroy();
self.shopBackButton = null;
}
if (self.shopBackText) {
self.shopBackText.destroy();
self.shopBackText = null;
}
// Remove all shop icons
if (self.shopIcons) {
for (var i = 0; i < self.shopIcons.length; i++) {
if (self.shopIcons[i]) {
self.shopIcons[i].destroy();
}
}
self.shopIcons = [];
}
// Remove all shop texts
if (self.shopTexts) {
for (var i = 0; i < self.shopTexts.length; i++) {
if (self.shopTexts[i]) {
self.shopTexts[i].destroy();
}
}
self.shopTexts = [];
}
// Remove all shop buy buttons
if (self.shopBuyButtons) {
for (var i = 0; i < self.shopBuyButtons.length; i++) {
if (self.shopBuyButtons[i]) {
self.shopBuyButtons[i].destroy();
}
}
self.shopBuyButtons = [];
}
// Remove all shop buy texts
if (self.shopBuyTexts) {
for (var i = 0; i < self.shopBuyTexts.length; i++) {
if (self.shopBuyTexts[i]) {
self.shopBuyTexts[i].destroy();
}
}
self.shopBuyTexts = [];
}
// Remove all shop children that were added
for (var i = self.children.length - 1; i >= 0; i--) {
var child = self.children[i];
// Remove shop-related elements
if (child.setText && child.text && (child.text.includes('TIENDA') || child.text.includes('POCION') || child.text.includes('ESCUDO') || child.text.includes('ESPADA') || child.text.includes('COMPRAR'))) {
child.destroy();
}
}
self.shopMode = false;
// Reset to show main menu elements
self.visible = true;
};
self.showDeck = function () {
if (!self.deckOverlay) {
// Ensure spellDeck exists before creating overlay
if (!self.spellDeck) self.spellDeck = new SpellDeck();
self.deckOverlay = self.createMenuOverlay(0x1a0a2e);
self.deckTitle = self.createMenuText('DECK DE HECHIZOS', 2048 / 2, 600, 100, 0xFFD700);
self.initializeDeckArrays();
self.refreshDeckDisplay();
self.deckBackButton = self.createMenuButton('coin', 2048 / 2, 2500, 0x00FF00);
self.deckBackText = self.createMenuText('VOLVER', 2048 / 2, 2500, 80, 0xFFFFFF);
}
self.deckOverlay.visible = true;
self.deckMode = true;
self.refreshDeckDisplay();
};
self.initializeDeckArrays = function () {
if (!self.deckElements) self.deckElements = [];
if (!self.availableElements) self.availableElements = [];
};
self.refreshDeckDisplay = function () {
if (!self.spellDeck) {
self.spellDeck = new SpellDeck();
}
// Clear existing deck elements
for (var i = 0; i < self.deckElements.length; i++) {
if (self.deckElements[i] && self.deckElements[i].parent) {
self.deckElements[i].destroy();
}
}
self.deckElements = [];
// Clear existing available elements
for (var i = 0; i < self.availableElements.length; i++) {
if (self.availableElements[i] && self.availableElements[i].parent) {
self.availableElements[i].destroy();
}
}
self.availableElements = [];
// Add helpful instructions at the top
var instructionText = new Text2('TOCA CARTAS PARA AÑADIR/QUITAR DEL DECK', {
size: 50,
fill: 0x00FF00,
font: "monospace"
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 700;
self.addChild(instructionText);
self.deckElements.push(instructionText);
// Display current deck (top section)
var deckLabel = new Text2('MI DECK ACTUAL (' + self.spellDeck.currentDeck.length + '/5):', {
size: 70,
fill: 0xFFD700,
font: "monospace"
});
deckLabel.anchor.set(0.5, 0.5);
deckLabel.x = 2048 / 2;
deckLabel.y = 800;
self.addChild(deckLabel);
self.deckElements.push(deckLabel);
// Add deck instruction
var deckInstruction = new Text2('TOCA PARA QUITAR', {
size: 40,
fill: 0xFF6666,
font: "monospace"
});
deckInstruction.anchor.set(0.5, 0.5);
deckInstruction.x = 2048 / 2;
deckInstruction.y = 850;
self.addChild(deckInstruction);
self.deckElements.push(deckInstruction);
// Display deck cards with better spacing
for (var i = 0; i < 5; i++) {
var cardX = 200 + i * 350;
var cardY = 1050;
if (i < self.spellDeck.currentDeck.length) {
var spell = self.spellDeck.getSpell(self.spellDeck.currentDeck[i]);
if (spell) {
// Card background with dynamic state visualization
var cardBg = self.attachAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.5,
scaleY: 4.5
});
// Check card status for visual feedback
var canCast = _canCastSpell(spell.id);
var currentTick = LK.ticks || 0;
var isOnCooldown = cardCooldowns[spell.id] && currentTick < cardCooldowns[spell.id];
var hasEnoughMana = currentMana >= (spell.manaCost || 0);
// Set card appearance based on status - simplified without animations
if (isOnCooldown) {
// Cooldown state - blue tint with reduced opacity
cardBg.tint = 0x4169E1;
cardBg.alpha = 0.5;
} else if (!hasEnoughMana) {
// Insufficient mana - red tint
cardBg.tint = 0xFF4444;
cardBg.alpha = 0.6;
} else if (canCast) {
// Ready to cast - normal appearance
cardBg.tint = self.spellDeck.getRarityColor(spell.rarity);
cardBg.alpha = 0.9;
} else {
// Default disabled state
cardBg.tint = 0x666666;
cardBg.alpha = 0.4;
}
cardBg.spellId = spell.id;
cardBg.isDeckCard = true;
self.deckElements.push(cardBg);
// Add border glow
var glowBorder = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 4,
scaleY: 5
});
glowBorder.tint = 0x00FF00;
glowBorder.alpha = 0.3;
self.deckElements.push(glowBorder);
// Card name
var cardName = new Text2(spell.name, {
size: 35,
fill: 0xFFFFFF,
font: "monospace"
});
cardName.anchor.set(0.5, 0.5);
cardName.x = cardX;
cardName.y = cardY - 60;
self.addChild(cardName);
self.deckElements.push(cardName);
// Card description
var description = spell.description || 'Hechizo magico';
var cardDesc = new Text2(description, {
size: 25,
fill: 0xCCCCCC,
font: "monospace",
wordWrap: true,
wordWrapWidth: 250
});
cardDesc.anchor.set(0.5, 0.5);
cardDesc.x = cardX;
cardDesc.y = cardY + 20;
self.addChild(cardDesc);
self.deckElements.push(cardDesc);
// Enhanced card stats with status indicators
var statsText = '';
if (spell.damage) statsText += 'Daño: ' + spell.damage + '\n';
if (spell.healing) statsText += 'Cura: ' + spell.healing + '\n';
if (spell.manaCost) {
var manaColor = currentMana >= spell.manaCost ? '✓' : '✗';
statsText += 'Mana: ' + spell.manaCost + ' ' + manaColor;
}
if (statsText) {
var cardStats = new Text2(statsText, {
size: 20,
fill: hasEnoughMana ? 0xFFD700 : 0xFF4444,
font: "monospace"
});
cardStats.anchor.set(0.5, 0.5);
cardStats.x = cardX;
cardStats.y = cardY + 80;
self.addChild(cardStats);
self.deckElements.push(cardStats);
}
// Add simple cooldown indicator if on cooldown
if (isOnCooldown) {
var timeRemaining = Math.ceil((cardCooldowns[spell.id] - currentTick) / 60);
var cooldownIndicator = new Text2('RECARGA: ' + timeRemaining + 's', {
size: 28,
fill: 0xFFFFFF,
font: "monospace"
});
cooldownIndicator.anchor.set(0.5, 0.5);
cooldownIndicator.x = cardX;
cooldownIndicator.y = cardY + 110;
self.addChild(cooldownIndicator);
self.deckElements.push(cooldownIndicator);
}
}
} else {
// Empty slot
var emptySlot = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.5,
scaleY: 4.5
});
emptySlot.tint = 0x444444;
emptySlot.alpha = 0.5;
self.deckElements.push(emptySlot);
var emptyText = new Text2('VACIO', {
size: 40,
fill: 0x666666,
font: "monospace"
});
emptyText.anchor.set(0.5, 0.5);
emptyText.x = cardX;
emptyText.y = cardY;
self.addChild(emptyText);
self.deckElements.push(emptyText);
}
}
// Display available spells (bottom section)
var availableLabel = new Text2('HECHIZOS DISPONIBLES PARA AÑADIR:', {
size: 60,
fill: 0xFFD700,
font: "monospace"
});
availableLabel.anchor.set(0.5, 0.5);
availableLabel.x = 2048 / 2;
availableLabel.y = 1350;
self.addChild(availableLabel);
self.availableElements.push(availableLabel);
// Add available instruction
var availableInstruction = new Text2('TOCA PARA AÑADIR A TU DECK', {
size: 40,
fill: 0x66FF66,
font: "monospace"
});
availableInstruction.anchor.set(0.5, 0.5);
availableInstruction.x = 2048 / 2;
availableInstruction.y = 1400;
self.addChild(availableInstruction);
self.availableElements.push(availableInstruction);
// Display available spells
var availableSpells = [];
for (var i = 0; i < self.spellDeck.availableSpells.length; i++) {
var spell = self.spellDeck.availableSpells[i];
if (self.spellDeck.currentDeck.indexOf(spell.id) === -1) {
availableSpells.push(spell);
}
}
if (availableSpells.length === 0) {
var noSpellsText = new Text2('NO HAY HECHIZOS DISPONIBLES\nDESBLOQUEA MAS JUGANDO', {
size: 50,
fill: 0x888888,
font: "monospace"
});
noSpellsText.anchor.set(0.5, 0.5);
noSpellsText.x = 2048 / 2;
noSpellsText.y = 1600;
self.addChild(noSpellsText);
self.availableElements.push(noSpellsText);
}
for (var i = 0; i < availableSpells.length && i < 8; i++) {
var spell = availableSpells[i];
var cardX = 150 + i % 4 * 450;
var cardY = 1550 + Math.floor(i / 4) * 400;
// Card background with hover effect
var cardBg = self.attachAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 4.2
});
cardBg.tint = self.spellDeck.getRarityColor(spell.rarity);
cardBg.spellId = spell.id;
cardBg.isDeckCard = false;
cardBg.alpha = 0.8;
self.availableElements.push(cardBg);
// Add selection glow
var selectionGlow = self.attachAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.7,
scaleY: 4.7
});
selectionGlow.tint = 0x00FFFF;
selectionGlow.alpha = 0.2;
self.availableElements.push(selectionGlow);
// Card name
var cardName = new Text2(spell.name, {
size: 32,
fill: 0xFFFFFF,
font: "monospace"
});
cardName.anchor.set(0.5, 0.5);
cardName.x = cardX;
cardName.y = cardY - 60;
self.addChild(cardName);
self.availableElements.push(cardName);
// Card description
var cardDesc = new Text2(spell.description, {
size: 22,
fill: 0xCCCCCC,
font: "monospace",
wordWrap: true,
wordWrapWidth: 280
});
cardDesc.anchor.set(0.5, 0.5);
cardDesc.x = cardX;
cardDesc.y = cardY + 10;
self.addChild(cardDesc);
self.availableElements.push(cardDesc);
// Card stats
var statsText = '';
if (spell.damage) statsText += 'Daño: ' + spell.damage + '\n';
if (spell.healing) statsText += 'Cura: ' + spell.healing + '\n';
if (spell.manaCost) statsText += 'Mana: ' + spell.manaCost;
if (statsText) {
var cardStats = new Text2(statsText, {
size: 18,
fill: 0xFFD700,
font: "monospace"
});
cardStats.anchor.set(0.5, 0.5);
cardStats.x = cardX;
cardStats.y = cardY + 70;
self.addChild(cardStats);
self.availableElements.push(cardStats);
}
// Rarity indicator
var rarityText = new Text2(spell.rarity.toUpperCase(), {
size: 20,
fill: self.spellDeck.getRarityColor(spell.rarity),
font: "monospace"
});
rarityText.anchor.set(0.5, 0.5);
rarityText.x = cardX;
rarityText.y = cardY + 100;
self.addChild(rarityText);
self.availableElements.push(rarityText);
}
};
self.hideDeck = function () {
if (self.deckOverlay) {
self.deckOverlay.destroy();
self.deckOverlay = null;
}
// Remove deck title
if (self.deckTitle) {
self.deckTitle.destroy();
self.deckTitle = null;
}
// Remove deck back button elements
if (self.deckBackButton) {
self.deckBackButton.destroy();
self.deckBackButton = null;
}
if (self.deckBackText) {
self.deckBackText.destroy();
self.deckBackText = null;
}
// Clear deck elements
for (var i = 0; i < self.deckElements.length; i++) {
if (self.deckElements[i] && self.deckElements[i].parent) {
self.deckElements[i].destroy();
}
}
self.deckElements = [];
// Clear available elements
for (var i = 0; i < self.availableElements.length; i++) {
if (self.availableElements[i] && self.availableElements[i].parent) {
self.availableElements[i].destroy();
}
}
self.availableElements = [];
// Remove all deck-related children
for (var i = self.children.length - 1; i >= 0; i--) {
var child = self.children[i];
if (child.setText && child.text && (child.text.includes('DECK') || child.text.includes('HECHIZOS') || child.text.includes('ACTUAL') || child.text.includes('DISPONIBLES'))) {
child.destroy();
}
}
self.deckMode = false;
self.visible = true;
};
// Unified UI factory for all menu elements
// Menu overlay creation function
self.createMenuOverlay = function (color) {
var overlay = self.attachAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 25.0,
scaleY: 35.0
});
overlay.alpha = 0.9;
overlay.tint = color || 0x000000;
overlay.interactive = true;
overlay.zIndex = 1000;
return overlay;
};
// Menu text creation function
self.createMenuText = function (text, x, y, size, color) {
var textElement = new Text2(text, {
size: size,
fill: color,
font: "monospace"
});
textElement.anchor.set(0.5, 0.5);
textElement.x = x;
textElement.y = y;
self.addChild(textElement);
return textElement;
};
// Menu button creation function
self.createMenuButton = function (asset, x, y, color) {
var button = self.attachAsset(asset, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 2,
scaleY: 2
});
button.tint = color;
return button;
};
// Removed redundant createUIElement - use specific creation methods instead
self.getShopItemsData = function () {
return [{
name: 'POCION SALUD',
description: 'Restaura 50 HP',
cost: 10,
icon: 'energySphere'
}, {
name: 'ESCUDO MAGICO',
description: 'Bloquea 3 ataques',
cost: 15,
icon: 'shield'
}, {
name: 'ESPADA MALDITA',
description: 'Daño x2 por 30s',
cost: 20,
icon: 'spell'
}];
};
self.initializeShopArrays = function () {
if (!self.shopIcons) self.shopIcons = [];
if (!self.shopTexts) self.shopTexts = [];
if (!self.shopBuyButtons) self.shopBuyButtons = [];
if (!self.shopBuyTexts) self.shopBuyTexts = [];
};
self.createShopItems = function (shopItems) {
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var yPos = 1100 + i * 200;
var itemIcon = self.attachAsset(item.icon, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 300,
y: yPos,
scaleX: 2,
scaleY: 2
});
itemIcon.tint = 0xFFD700;
self.shopIcons.push(itemIcon);
var itemText = new Text2(item.name + '\n' + item.description + '\nCosto: ' + item.cost + ' monedas', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
itemText.anchor.set(0, 0.5);
itemText.x = 2048 / 2 - 200;
itemText.y = yPos;
self.addChild(itemText);
self.shopTexts.push(itemText);
var buyButton = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 300,
y: yPos,
scaleX: 2,
scaleY: 2
});
buyButton.tint = 0x00FF00;
buyButton.itemIndex = i;
self.shopBuyButtons.push(buyButton);
var buyText = new Text2('COMPRAR', {
size: 50,
fill: 0xFFFFFF,
font: "monospace"
});
buyText.anchor.set(0.5, 0.5);
buyText.x = 2048 / 2 + 300;
buyText.y = yPos;
self.addChild(buyText);
self.shopBuyTexts.push(buyText);
}
};
self.startGame = function () {
// Check if this is a new player (no tutorial completed)
if (!storage.tutorialCompleted && tutorial) {
// Show tutorial for new players
self.visible = false;
// Small delay to ensure menu is hidden before tutorial starts
tween({}, {}, {
duration: 100,
onFinish: function onFinish() {
tutorial.startTutorial();
}
});
// Tutorial started successfully
return;
}
// UNIFIED DECK SYNCHRONIZATION: Fix all deck system inconsistencies
// Create unified deck reference that all systems will use
var unifiedDeck = [];
// Priority 1: Use spellDeck from menu if it exists and has content
if (self.spellDeck && self.spellDeck.currentDeck && self.spellDeck.currentDeck.length > 0) {
unifiedDeck = self.spellDeck.currentDeck.slice();
}
// Priority 2: Use storage deck if available
else if (storage.spellDeck && storage.spellDeck.length > 0) {
unifiedDeck = storage.spellDeck.slice();
}
// Priority 3: Use global currentDeck if available
else if (currentDeck && currentDeck.length > 0) {
unifiedDeck = currentDeck.slice();
}
// Priority 4: Use activeSpellDeck if available
else if (activeSpellDeck && activeSpellDeck.currentDeck && activeSpellDeck.currentDeck.length > 0) {
unifiedDeck = activeSpellDeck.currentDeck.slice();
}
// Priority 5: Default deck as last resort
else {
unifiedDeck = ['fireball', 'heal', 'lightning'];
}
// SYNCHRONIZE ALL DECK SYSTEMS with unified deck
currentDeck = unifiedDeck.slice();
activeSpellDeck.currentDeck = unifiedDeck.slice();
storage.spellDeck = unifiedDeck.slice();
if (self.spellDeck) {
self.spellDeck.currentDeck = unifiedDeck.slice();
}
// Debug: Log deck synchronization with enhanced details
console.log('=== DECK SYNCHRONIZATION ===');
console.log('Unified deck:', unifiedDeck);
console.log('ActiveSpellDeck sync:', activeSpellDeck.currentDeck);
console.log('Storage sync:', storage.spellDeck);
console.log('CurrentDeck sync:', currentDeck);
console.log('Available spells IDs:', availableSpells.map(function (s) {
return s.id;
}));
console.log('Spell validation:');
for (var d = 0; d < unifiedDeck.length; d++) {
var spellId = unifiedDeck[d];
var spell = _getSpell(spellId);
console.log(' - ' + spellId + ':', spell ? spell.name : 'NOT FOUND');
}
// Hide menu and start game normally
self.visible = false;
gameStarted = true;
// Show cave background when game starts
if (backgroundMap) {
backgroundMap.visible = true;
}
// Show all game elements
wizard.visible = true;
for (var i = 0; i < paths.length; i++) {
paths[i].visible = true;
}
// Show all stone path segments and make them visible
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child.pathIndex !== undefined && child !== paths[child.pathIndex]) {
child.visible = true;
// Check if it's a stone path segment or path number
if (child.alpha !== undefined && child.setText === undefined) {
child.alpha = 0; // Keep stone paths invisible
}
}
}
coinText.visible = true;
killCountText.visible = true;
tapText.visible = true;
healthBarBg.visible = true;
healthBar.visible = true;
healthText.visible = true;
// Show mana UI
manaBarBg.visible = true;
manaBar.visible = true;
manaText.visible = true;
updateManaDisplay();
// Show card toggle button
cardToggleButton.visible = true;
cardToggleText.visible = true;
// Spell UI system removed - using deck menu only
console.log('Spell UI handled through deck menu system');
// SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting
// Spells can now only be cast from the deck menu interface
console.log('Spell slots eliminated - using deck menu system only');
// Mana system simplified - values set directly
currentMana = 100;
maxMana = 100;
if (activeSpellDeck) {
activeSpellDeck.currentMana = 100;
activeSpellDeck.maxMana = 100;
}
console.log('=== MANA SYSTEM SIMPLIFIED ===');
console.log('currentMana:', currentMana);
console.log('activeSpellDeck.currentMana:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
console.log('maxMana:', maxMana);
// Mana bar functionality moved to spell deck system
console.log('Mana system managed by deck menu');
// DEBUG: Verify mana values after initialization
console.log('=== MANA FORCE INITIALIZATION ===');
console.log('currentMana:', currentMana);
console.log('activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
console.log('maxMana:', maxMana);
console.log('Mana bar updated successfully');
// SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting
// Spells can now only be cast from the deck menu interface
console.log('Spell slots eliminated - using deck menu system only');
// CRITICAL MANA SYSTEM INITIALIZATION AND REPAIR
console.log('=== CRITICAL MANA INITIALIZATION ===');
// STEP 1: Force mana to 100 immediately
currentMana = 100;
maxMana = 100;
// STEP 2: Force activeSpellDeck mana to 100
if (activeSpellDeck) {
activeSpellDeck.currentMana = 100;
activeSpellDeck.maxMana = 100;
}
// STEP 3: Clear any corrupted cooldowns
cardCooldowns = {};
// STEP 4: Force mana validation and repair
validateManaSystem();
// STEP 5: Update mana display immediately
updateManaDisplay();
// STEP 6: Additional verification with delay to ensure all systems sync
tween({}, {}, {
duration: 100,
onFinish: function onFinish() {
// FORCE MANA TO 100 AGAIN after all initialization
currentMana = 100;
if (activeSpellDeck) {
activeSpellDeck.currentMana = 100;
}
// Force update display again
updateManaDisplay();
// Final debug verification
console.log('=== FINAL MANA VERIFICATION ===');
console.log('Final currentMana:', currentMana);
console.log('Final activeSpellDeck.currentMana:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
console.log('Final maxMana:', maxMana);
console.log('Mana system successfully forced to 100');
// Additional safety check - validate one more time
if (currentMana !== 100) {
console.log('⚠️ EMERGENCY MANA FIX');
currentMana = 100;
if (activeSpellDeck) activeSpellDeck.currentMana = 100;
updateManaDisplay();
}
}
});
// SPELL SLOTS SYSTEM REMOVED
// All spell casting now happens through the deck menu system only
console.log('=== SPELL SYSTEM SIMPLIFIED ===');
console.log('Deck menu is the only way to cast spells');
console.log('Spell slots have been eliminated');
// Start medieval music with user's volume setting
var musicVolume = storage.musicVolume || 0.7;
LK.playMusic('medievalTheme', {
volume: musicVolume,
fade: {
start: 0,
end: musicVolume,
duration: 2000
}
});
};
return self;
});
var Orb = Container.expand(function () {
var self = Container.call(this);
// Create orb visual using energy sphere
var orbGraphics = self.attachAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
orbGraphics.tint = 0xFFD700; // Golden color for orbs
orbGraphics.alpha = 0.9;
self.orbitalAngle = 0; // Starting angle for this orb
self.orbitalRadius = 880; // Distance from wizard - doubled again for even more separation
self.rotationSpeed = 0.025; // How fast orbs rotate - halved for slower movement
// Category 3: Orb-enemy collisions (separate from projectile-enemy and enemy-wizard)
self.processOrbEnemyCollisions = function () {
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
// Skip collision check with wizard
if (enemy === wizard) {
continue;
}
// Initialize collision tracking for this enemy if not exists
if (!self.lastIntersecting) {
self.lastIntersecting = {};
}
if (self.lastIntersecting[i] === undefined) {
self.lastIntersecting[i] = false;
}
// 1.1 Distance Culling: Quick distance check before expensive collision detection
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxOrbRange = 80; // Orb collision range
var currentIntersecting = false;
if (distance <= maxOrbRange) {
// Only perform expensive intersection test if enemy is close enough
currentIntersecting = self.intersects(enemy);
}
if (!self.lastIntersecting[i] && currentIntersecting) {
// Deal damage to enemy on contact transition (first contact only)
enemy.takeDamage(200);
// Visual effect for orb hit
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Create orb impact effect
var orbImpact = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 0.3,
scaleY: 0.3
}));
orbImpact.tint = 0xFFD700;
orbImpact.alpha = 0.8;
// Animate orb impact
globalTween(orbImpact, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
orbImpact.destroy();
}
});
}
// Update collision state for this enemy
self.lastIntersecting[i] = currentIntersecting;
}
};
self.update = function () {
// Pause orb when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Rotate around wizard
if (wizard) {
self.orbitalAngle += self.rotationSpeed;
self.x = wizard.x + Math.cos(self.orbitalAngle) * self.orbitalRadius;
self.y = wizard.y + Math.sin(self.orbitalAngle) * self.orbitalRadius - 240; // Position orb much higher up
}
// Add pulsing effect
var pulse = 1 + Math.sin(LK.ticks * 0.3) * 0.2;
orbGraphics.scaleX = 0.4 * pulse;
orbGraphics.scaleY = 0.4 * pulse;
// Category 3: Orb-enemy collisions (separate from projectile-enemy and enemy-wizard)
self.processOrbEnemyCollisions();
};
return self;
});
// Create global death handler instance
var Projectile = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'projectile';
self.targetEnemy = null;
self.hitEnemy = false;
// Unified projectile configuration
var configs = {
projectile: {
assetId: 'projectile',
scale: 1.5,
speed: 50,
damage: 100,
tint: 0x44aaff,
hasGlow: true,
hasRotation: true
},
energyBeam: {
assetId: 'projectileGlow',
scale: 1.0,
speed: 60,
damage: 100,
tint: 0x00ffff,
hasRotation: true
},
fireBall: {
assetId: 'projectileGlow',
scale: 1.5,
speed: 40,
damage: 150,
tint: 0xFF4500,
hasRotation: true,
hasFlicker: true,
isAreaDamage: true
}
};
var config = configs[self.type] || configs.projectile;
self.speed = config.speed;
self.damage = config.damage;
self.direction = {
x: 0,
y: 0
};
// Create unified graphics
self.graphics = self.attachAsset(config.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: config.scale,
scaleY: config.scale
});
self.graphics.tint = config.tint;
// Add glow effect if specified
if (config.hasGlow) {
var glow = self.attachAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
glow.alpha = 0.3;
glow.tint = config.tint;
}
self.update = function () {
if (tutorial && tutorial.isActive) return;
// Move projectile
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Visual effects
if (config.hasRotation) {
var angle = Math.atan2(self.direction.y, self.direction.x);
self.graphics.rotation = angle + Math.PI / 2;
}
if (config.hasFlicker) {
var flicker = 1 + Math.sin(LK.ticks * 0.4) * 0.3;
self.graphics.scaleX = config.scale * flicker;
self.graphics.scaleY = config.scale * flicker;
}
// Remove if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
ProjectileFactory.removeProjectile(self);
return;
}
self.checkCollisions();
};
self.checkCollisions = function () {
if (self.hitEnemy) return;
if (config.isAreaDamage) {
self.processAreaCollisions();
} else {
self.processSingleTargetCollisions();
}
};
// Unified collision processing
self.processAreaCollisions = function () {
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy === wizard || enemy.isDying) continue;
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= 120 && self.intersects(enemy)) {
enemy.takeDamage(self.damage);
self.createExplosion(enemy);
self.hitEnemy = true;
ProjectileFactory.removeProjectile(self);
return;
}
}
};
self.processSingleTargetCollisions = function () {
if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDying) {
var distance = Math.sqrt(Math.pow(self.targetEnemy.x - self.x, 2) + Math.pow(self.targetEnemy.y - self.y, 2));
if (distance <= 100 && self.intersects(self.targetEnemy)) {
self.targetEnemy.takeDamage(self.damage);
self.hitEnemy = true;
ProjectileFactory.removeProjectile(self);
return;
}
} else {
self.hitEnemy = true;
ProjectileFactory.removeProjectile(self);
}
};
self.createExplosion = function (enemy) {
LK.effects.flashObject(enemy, 0xFF4500, 400);
var explosion = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 2,
scaleY: 2
}));
explosion.tint = 0xFF6600;
explosion.alpha = 0.8;
globalTween(explosion, {
scaleX: 5,
scaleY: 5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
};
return self;
});
// Simple effects helper for basic visual feedback
// Simple SpellDeck class to replace the complex system
var SpellDeck = Container.expand(function () {
var self = Container.call(this);
// Use the existing simple spell system
self.currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning'];
self.availableSpells = availableSpells; // Reference to global availableSpells
// Simple methods to match the interface expected by GameMenu
self.addToDeck = function (spellId) {
if (self.currentDeck.length >= 5) return false;
if (self.currentDeck.indexOf(spellId) !== -1) return false;
self.currentDeck.push(spellId);
storage.spellDeck = self.currentDeck.slice();
return true;
};
self.removeFromDeck = function (spellId) {
var index = self.currentDeck.indexOf(spellId);
if (index === -1) return false;
self.currentDeck.splice(index, 1);
storage.spellDeck = self.currentDeck.slice();
return true;
};
self.getSpell = function (spellId) {
return _getSpell(spellId);
};
self.getRarityColor = function (rarity) {
return _getRarityColor(rarity);
};
self.unlockSpell = function (spellId) {
// Simple unlock system - spells are always available
return true;
};
return self;
});
// Simple spell system - no complex classes needed
var Tutorial = Container.expand(function () {
var self = Container.call(this);
// Simple tutorial state
self.isActive = false;
// Tutorial overlay background
var tutorialOverlay = self.attachAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 25.0,
scaleY: 35.0
});
tutorialOverlay.alpha = 0.9;
tutorialOverlay.tint = 0x000000;
tutorialOverlay.visible = false; // Initially hidden
tutorialOverlay.zIndex = 1999; // Ensure proper layering
tutorialOverlay.interactive = true; // Always interactive to block clicks
// Start the tutorial - simplified version
self.startTutorial = function () {
self.isActive = true;
// Make tutorial visible
self.visible = true;
self.zIndex = 2000;
tutorialOverlay.visible = true;
// Hide game menu while tutorial is active
if (gameMenu) {
gameMenu.visible = false;
}
// Show simple tutorial text
var titleText = new Text2('WIZARD DEFENDER', {
size: 120,
fill: 0xFFD700,
font: "monospace"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 1000;
self.addChild(titleText);
var instructionsText = new Text2('TOCA ENEMIGOS PARA ATACAR\nUSA CARTAS PARA HECHIZOS\nCUIDA TU SALUD', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 2048 / 2;
instructionsText.y = 1400;
self.addChild(instructionsText);
var continueText = new Text2('TOCA PARA EMPEZAR', {
size: 60,
fill: 0x00FF00,
font: "monospace"
});
continueText.anchor.set(0.5, 0.5);
continueText.x = 2048 / 2;
continueText.y = 1800;
self.addChild(continueText);
return true; // Tutorial started
};
// Complete the tutorial - simplified
self.completeTutorial = function () {
// Mark tutorial as completed
storage.tutorialCompleted = true;
// Hide tutorial completely
tutorialOverlay.visible = false;
tutorialOverlay.interactive = false;
self.visible = false;
self.isActive = false;
// Start game immediately
if (gameMenu) {
gameMenu.startGame();
}
};
// Handle tutorial interactions - simplified
self.down = function (x, y, obj) {
if (!self.isActive) return;
// Any tap completes tutorial
self.completeTutorial();
};
return self;
});
// Impact effect function now handled by BaseDamageHandler
// Unified death handler for all enemy types using enemy configuration
var UnifiedDeathHandler = Container.expand(function () {
var self = Container.call(this);
// Execute enemy death with consolidated coin and reward logic
self.executeEnemyDeath = function (enemy, enemyArray) {
enemy.animationState = 'dying';
enemy.currentFrame = 3;
LK.getSound('painSound').play();
enemy.isDying = true;
// Use direct enemy configuration values based on type
var deathRotation = Math.PI * 0.5;
var numCoins = 1;
// Set specific values based on enemy type
if (enemy.enemyType === 'miniBoss') {
deathRotation = Math.PI * 0.8;
numCoins = 5;
} else if (enemy.enemyType === 'ogre') {
deathRotation = Math.PI * 0.6;
numCoins = 1;
} else if (enemy.enemyType === 'knight') {
deathRotation = Math.PI * 0.7;
numCoins = 1;
}
// Special cleanup for mini boss UI elements
if (enemy.enemyType === 'miniBoss') {
self.cleanupMiniBossUI(enemy);
}
// Execute unified death animation
globalTween(enemy, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: deathRotation
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.handleDeathRewards(enemy, numCoins);
self.updateProgression(enemy);
self.cleanupEnemy(enemy, enemyArray);
}
});
};
// Clean up mini boss UI elements
self.cleanupMiniBossUI = function (enemy) {
if (enemy.healthBarBg && enemy.healthBarBg.parent) {
enemy.healthBarBg.destroy();
}
if (enemy.healthBarFg && enemy.healthBarFg.parent) {
enemy.healthBarFg.destroy();
}
if (enemy.healthText && enemy.healthText.parent) {
enemy.healthText.destroy();
}
};
// Handle coin drops and difficulty-based rewards using pooled coins
self.handleDeathRewards = function (enemy, numCoins) {
var selectedDifficulty = storage.difficulty || 'NORMAL';
for (var coinIdx = 0; coinIdx < numCoins; coinIdx++) {
// Create coin directly without object pool
var coin = new Coin();
coin.x = enemy.x + (enemy.enemyType === 'miniBoss' ? (Math.random() - 0.5) * 200 : 0);
coin.y = enemy.y - 50 + (enemy.enemyType === 'miniBoss' ? (Math.random() - 0.5) * 100 : 0);
coin.isAnimating = true;
coin.bobOffset = Math.random() * Math.PI * 2;
coin.initialY = 0;
coin.visible = true;
coin.alpha = 1.0;
game.addChild(coin);
coins.push(coin);
var coinTargetX = 120 + coinText.width / 2;
var coinTargetY = 90 + coinText.height / 2;
globalTween(coin, {
x: coinTargetX,
y: coinTargetY,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000 + (enemy.enemyType === 'miniBoss' ? coinIdx * 200 : 0),
easing: tween.easeOut,
onFinish: function onFinish() {
self.processCoinReward(enemy, selectedDifficulty, coin);
}
});
}
};
// Process coin rewards with difficulty modifiers
self.processCoinReward = function (enemy, selectedDifficulty, coin) {
var coinReward = enemy.enemyType === 'miniBoss' ? 10 : 1;
if (selectedDifficulty === 'FACIL') {
coinReward = Math.floor(coinReward * 1.5);
} else if (selectedDifficulty === 'DIFICIL') {
coinReward = Math.max(1, Math.floor(coinReward * 0.75));
}
coinCounter += coinReward;
coinText.setText('Coins: ' + coinCounter);
// Easy difficulty healing bonus
if (selectedDifficulty === 'FACIL' && Math.random() < 0.15) {
wizard.health = Math.min(wizard.health + 5, wizard.maxHealth);
updateHealthBar();
LK.effects.flashObject(wizard, 0x00FF00, 200);
}
// Remove coin from tracking array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === coin) {
coins.splice(i, 1);
break;
}
}
// Destroy coin directly without object pool
coin.destroy();
};
// Update kill counter and experience progression
self.updateProgression = function (enemy) {
var killIncrement = enemy.enemyType === 'miniBoss' ? 10 : 1;
enemyKillCounter += killIncrement;
killCountText.setText('Puntuacion: ' + enemyKillCounter);
wizard.gainExperience(enemy.enemyType === 'miniBoss' ? 250 : 25);
if (selectedEnemy === enemy) {
selectedEnemy = null;
}
};
// Clean up enemy from arrays and game
self.cleanupEnemy = function (enemy, enemyArray) {
// Remove from appropriate array
for (var i = enemyArray.length - 1; i >= 0; i--) {
if (enemyArray[i] === enemy) {
enemyArray.splice(i, 1);
break;
}
}
// Also remove from global enemy manager collections
globalEnemyManager.removeFromCollection(enemy, enemy.enemyType);
// Remove from all legacy arrays to ensure proper cleanup
var allArrays = [enemies, ogres, knights, miniBosses];
for (var arrayIdx = 0; arrayIdx < allArrays.length; arrayIdx++) {
var array = allArrays[arrayIdx];
for (var i = array.length - 1; i >= 0; i--) {
if (array[i] === enemy) {
array.splice(i, 1);
break;
}
}
}
enemy.destroy();
// Set score reward based on enemy type
var scoreReward = 10;
if (enemy.enemyType === 'miniBoss') {
scoreReward = 100;
} else if (enemy.enemyType === 'ogre') {
scoreReward = 20;
} else if (enemy.enemyType === 'knight') {
scoreReward = 30;
}
LK.setScore(LK.getScore() + scoreReward);
};
return self;
});
// UpgradeMenu class removed - using spell deck system instead
// Unified Projectile Factory using consolidated GAME_CONFIG.projectiles
var Wizard = Container.expand(function () {
var self = Container.call(this);
// Animation system for wizard
self.currentFrame = 1;
self.animationTimer = 0;
self.animationSpeed = 18; // Change frame every 18 ticks (300ms at 60fps)
// Create all wizard graphics frames and store them
self.wizardFrames = [];
for (var i = 1; i <= 4; i++) {
var frameGraphics = self.attachAsset('wizard' + i, {
anchorX: 0.5,
anchorY: 1.0,
scaleX: 2.5,
scaleY: 2.5
});
frameGraphics.visible = i === 1; // Only show first frame initially
self.wizardFrames.push(frameGraphics);
}
// Create invisible hitbox with much smaller size for more precise collision
var hitbox = self.attachAsset('wizard1', {
anchorX: 0.3,
anchorY: 1.0,
scaleX: 0.25,
// Much smaller size for very precise collision
scaleY: 0.3 // Much smaller size for very precise collision
});
hitbox.alpha = 0; // Make hitbox invisible
// Position hitbox slightly to the right to reduce left side
hitbox.x = 15; // Offset hitbox to the right
// Create shield visual effect
self.shieldGraphics = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3
});
self.shieldGraphics.alpha = 0.7;
self.shieldGraphics.visible = false;
self.attackCooldown = 0;
self.level = 1;
self.health = 100;
self.maxHealth = 100;
self.shieldActive = false; // Track shield status
// Upgrade system removed - simplified wizard properties
// Override intersects method to use smaller hitbox
self.intersects = function (other) {
return hitbox.intersects(other);
};
self.update = function () {
// Pause wizard when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Update shield visibility based on shield status
self.shieldGraphics.visible = self.shieldActive;
if (self.shieldActive) {
// Animate shield with pulsing effect
var pulse = 1 + Math.sin(LK.ticks * 0.15) * 0.2;
self.shieldGraphics.scaleX = 3 * pulse;
self.shieldGraphics.scaleY = 3 * pulse;
// Slowly rotate shield
self.shieldGraphics.rotation += 0.03;
// Add glowing effect
self.shieldGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.1) * 0.2;
}
// Upgrade-based abilities removed - using spell deck system instead
// Simplified animation system - instant frame switching only
self.animationTimer++;
if (self.animationTimer >= self.animationSpeed) {
self.animationTimer = 0;
// Hide current frame
self.wizardFrames[self.currentFrame - 1].visible = false;
// Move to next frame
self.currentFrame++;
if (self.currentFrame > 4) {
self.currentFrame = 1;
}
// Show next frame
self.wizardFrames[self.currentFrame - 1].visible = true;
}
};
self.attack = function (direction) {
if (self.attackCooldown <= 0) {
// Default direction if none specified
if (direction === undefined) {
direction = 0; // Default to center path
}
// Get attack angle based on path direction
var attackAngle = pathAngles[direction];
var attackDistance = 100;
// Calculate spell position based on attack direction
var spellX = self.x + Math.cos(attackAngle) * attackDistance;
var spellY = self.y + Math.sin(attackAngle) * attackDistance;
// Create spell effect
var spell = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: spellX,
y: spellY,
scaleX: 0.5,
scaleY: 0.5
}));
// Animate spell with magical effects
globalTween(spell, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
spell.destroy();
}
});
// Add rotation animation to spell
globalTween(spell, {
rotation: Math.PI * 2
}, {
duration: 500,
easing: tween.linear
});
self.attackCooldown = 30; // 0.5 seconds at 60fps
LK.getSound('spellCast').play();
// Base damage for wizard attack
var totalDamage = 1;
// Attack enemies in the specified direction/path
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.pathIndex === direction) {
// Only hit enemies on exact same path - no distance validation
enemy.takeDamage(totalDamage);
}
}
// Attack ogres in the specified direction/path
for (var i = ogres.length - 1; i >= 0; i--) {
var ogre = ogres[i];
if (ogre.pathIndex === direction) {
// Only hit ogres on exact same path - no distance validation
ogre.takeDamage(totalDamage);
}
}
// Attack knights in the specified direction/path
for (var i = knights.length - 1; i >= 0; i--) {
var knight = knights[i];
if (knight.pathIndex === direction) {
// Only hit knights on exact same path - no distance validation
knight.takeDamage(totalDamage);
}
}
return true;
}
return false;
};
self.gainExperience = function (amount) {
self.experience += amount;
var expNeeded = self.level * 100;
if (self.experience >= expNeeded) {
self.levelUp();
}
};
self.levelUp = function () {
self.level++;
self.experience = 0;
// Visual level up effect
LK.effects.flashObject(self, 0xFFD700, 500);
};
self.takeDamage = function (damage) {
// Check if teleport invulnerability is active
if (self.teleportInvuln) {
GameManager.createFlashEffect(self, 0x8000FF, 200);
return;
}
// Check if shield is active
if (self.shieldActive) {
// Initialize shield properties if not set
if (self.maxShieldHits === undefined) {
self.maxShieldHits = 1;
self.currentShieldHits = 0;
}
// Increment shield hits
self.currentShieldHits++;
// Visual feedback for shield use
GameManager.createFlashEffect(self, 0x00BFFF, 300);
// Check if shield is depleted
if (self.currentShieldHits >= self.maxShieldHits) {
self.shieldActive = false;
// Start shield regeneration timer
var regenTime = self.shieldRegen ? 5000 : 10000; // Faster regen if improved
globalTween({}, {}, {
duration: regenTime,
onFinish: function onFinish() {
// Regenerate shield
self.shieldActive = true;
self.currentShieldHits = 0;
// Visual feedback for shield regeneration
GameManager.createFlashEffect(self, 0x00BFFF, 500);
// Add shield regeneration animation
globalTween(self.shieldGraphics, {
scaleX: 5,
scaleY: 5,
alpha: 1.0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
globalTween(self.shieldGraphics, {
scaleX: 3,
scaleY: 3,
alpha: 0.7
}, {
duration: 400,
easing: tween.easeIn
});
}
});
}
});
}
// No damage taken, shield absorbed it
return;
}
// Use unified damage handler for core damage logic
self.health -= damage;
GameManager.createFlashEffect(self, 0xFF0000, 200);
if (self.health <= 0) {
self.health = 0;
// 10% chance to revive when dying
var reviveChance = Math.random();
if (reviveChance < 0.10) {
// Revival successful!
self.health = Math.floor(self.maxHealth * 0.5); // Revive with 50% health
// Destroy ALL enemies when revival activates (no distance restriction)
var allEnemies = collisionArrayPool.getAllEnemies();
for (var enemyIdx = allEnemies.length - 1; enemyIdx >= 0; enemyIdx--) {
var enemy = allEnemies[enemyIdx];
// Create destruction effect for each enemy
GameManager.createFlashEffect(enemy, 0xFFD700, 500);
// Create golden explosion particles
GameManager.createVisualEffect('explosion', enemy, {
explosionColor: 0xFFD700,
explosionScale: 4.0
});
// Kill ALL enemies instantly by calling die() method
enemy.die();
}
// Visual effects for revival
LK.effects.flashScreen(0x00FF00, 1500); // Green flash for revival
GameManager.createFlashEffect(self, 0xFFD700, 1000); // Golden flash on wizard
// Create healing aura effect
GameManager.createVisualEffect('explosion', self, {
explosionColor: 0x00FF00,
explosionScale: 8.0
});
// Play spell cast sound for revival
LK.getSound('spellCast').play();
// Update health bar to show revival
updateHealthBar();
} else {
// Game over when health reaches 0 and no revival
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
}
// Update health bar
updateHealthBar();
// Simplified screen shake for better performance
var shakeIntensity = 8;
var originalX = game.x;
var originalY = game.y;
// Simple single shake effect
globalTween(game, {
x: originalX + shakeIntensity,
y: originalY + shakeIntensity * 0.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
globalTween(game, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeIn
});
}
});
};
// Force push ability removed - not used in current spell system
// Freeze pulse ability removed - not used in current spell system
// Thorns ability removed - not used in current spell system
// Fireball launch removed - using spell deck system instead
// Frame transition config removed - using simple animation only
// Advanced transition removed - using simple frame switching only
// Basic transition removed - using instant frame switching only
// Sparkle effects removed - using simplified animations only
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Black background for pixel art
});
/****
* Game Code
****/
// Simplified TweenManager for basic tween functionality
var TweenManager = {
// Core tween plugin reference
tweenPlugin: null,
// Initialize tween manager
initialize: function initialize() {
console.log('Initializing simplified TweenManager');
this.tweenPlugin = LK.import('@upit/tween.v1');
if (!this.tweenPlugin || typeof this.tweenPlugin !== 'function') {
console.warn('Tween plugin failed, creating fallback');
this.createFallbackPlugin();
} else {
console.log('Tween plugin loaded successfully');
}
// Make tween globally available
window.tween = this.createTweenProxy();
return this.isPluginValid();
},
// Check if plugin is valid
isPluginValid: function isPluginValid() {
return this.tweenPlugin && typeof this.tweenPlugin === 'function';
},
// Create simple fallback plugin
createFallbackPlugin: function createFallbackPlugin() {
var self = this;
this.tweenPlugin = function (target, props, options) {
// Immediate property changes for basic compatibility
if (target && props) {
for (var prop in props) {
if (target.hasOwnProperty(prop)) {
target[prop] = props[prop];
}
}
}
// Execute onFinish callback if provided
if (options && options.onFinish && typeof options.onFinish === 'function') {
setTimeout(function () {
try {
options.onFinish();
} catch (error) {
console.error('Fallback tween error:', error);
}
}, options.duration || 0);
}
return null;
};
// Add basic easing functions
var linear = function linear(t) {
return t;
};
this.tweenPlugin.easeOut = linear;
this.tweenPlugin.easeIn = linear;
this.tweenPlugin.easeInOut = linear;
this.tweenPlugin.linear = linear;
this.tweenPlugin.bounceOut = linear;
},
// Create tween proxy function
createTweenProxy: function createTweenProxy() {
var self = this;
var tweenProxy = function tweenProxy(target, props, options) {
return self.executeTween(target, props, options);
};
// Add easing functions to proxy
tweenProxy.easeOut = this.tweenPlugin ? this.tweenPlugin.easeOut : function (t) {
return t;
};
tweenProxy.easeIn = this.tweenPlugin ? this.tweenPlugin.easeIn : function (t) {
return t;
};
tweenProxy.easeInOut = this.tweenPlugin ? this.tweenPlugin.easeInOut : function (t) {
return t;
};
tweenProxy.linear = this.tweenPlugin ? this.tweenPlugin.linear : function (t) {
return t;
};
tweenProxy.bounceOut = this.tweenPlugin ? this.tweenPlugin.bounceOut : function (t) {
return t;
};
tweenProxy.stop = function (target, properties) {
return self.stopTween(target, properties);
};
return tweenProxy;
},
// Execute tween with basic error handling
executeTween: function executeTween(target, props, options) {
if (!target) {
console.warn('No target provided for tween');
return null;
}
if (!this.isPluginValid()) {
console.warn('Tween plugin not available, using fallback');
this.createFallbackPlugin();
}
try {
return this.tweenPlugin(target, props, options);
} catch (error) {
console.error('Tween execution failed:', error);
// Use LK.effects as fallback
if (props && props.alpha !== undefined) {
LK.effects.flashObject(target, 0xFFFFFF, options ? options.duration || 500 : 500);
}
if (options && options.onFinish && typeof options.onFinish === 'function') {
setTimeout(options.onFinish, options.duration || 0);
}
return null;
}
},
// Stop tween
stopTween: function stopTween(target, properties) {
if (!this.isPluginValid()) {
return;
}
try {
return this.tweenPlugin.stop(target, properties);
} catch (error) {
console.error('Stop tween failed:', error);
}
}
};
// Initialize TweenManager
TweenManager.initialize();
// Create global tween function
function globalTween(target, props, options) {
return TweenManager.executeTween(target, props, options);
}
// Legacy compatibility functions
function safeTween(target, props, options) {
return globalTween(target, props, options);
}
function validateTweenAvailability() {
return TweenManager.isPluginValid();
}
// Ensure tween is available globally
if (!window.tween) {
window.tween = TweenManager.createTweenProxy();
}
console.log('Simplified TweenManager initialized');
function _typeof2(o) {
"@babel/helpers - typeof";
return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof2(o);
}
var availableSpells = [{
id: 'fireball',
name: 'FIREBALL',
damage: 150,
manaCost: 30,
rarity: 'common',
description: 'Lanza bola de fuego explosiva'
}, {
id: 'heal',
name: 'HEAL',
healing: 50,
manaCost: 25,
rarity: 'common',
description: 'Restaura puntos de salud'
}, {
id: 'lightning',
name: 'LIGHTNING',
damage: 200,
manaCost: 40,
rarity: 'rare',
description: 'Cadena de rayos entre enemigos'
}];
// PASO 2: Función de validación de maná mejorada
function validateManaSystem() {
var manaWasCorrupted = false;
console.log('=== VALIDATING MANA SYSTEM ===');
console.log('currentMana (before):', currentMana, _typeof2(currentMana));
console.log('activeSpellDeck.currentMana (before):', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
// CRÍTICO: Validar currentMana con detección de corrupción
if (typeof currentMana !== 'number' || isNaN(currentMana) || currentMana < 0 || currentMana > 100) {
console.log('⚠️ currentMana corrupted, resetting to 100');
currentMana = 100;
manaWasCorrupted = true;
}
// CRÍTICO: Validar activeSpellDeck existe y tiene propiedades válidas
if (!activeSpellDeck) {
console.log('⚠️ activeSpellDeck missing, creating with full mana');
activeSpellDeck = {
currentMana: 100,
maxMana: 100
};
manaWasCorrupted = true;
}
// CRÍTICO: Validar activeSpellDeck.currentMana
if (typeof activeSpellDeck.currentMana !== 'number' || isNaN(activeSpellDeck.currentMana) || activeSpellDeck.currentMana < 0 || activeSpellDeck.currentMana > 100) {
console.log('⚠️ activeSpellDeck.currentMana corrupted, resetting to 100');
activeSpellDeck.currentMana = 100;
activeSpellDeck.maxMana = 100;
manaWasCorrupted = true;
}
// CRÍTICO: Sincronizar si hay diferencias entre las dos variables
if (Math.abs(currentMana - activeSpellDeck.currentMana) > 1) {
console.log('⚠️ Mana desync detected - currentMana:', currentMana, 'activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
// Usar el valor más alto pero válido
var validMana = Math.max(currentMana, activeSpellDeck.currentMana);
if (validMana < 0 || validMana > 100 || typeof validMana !== 'number' || isNaN(validMana)) {
validMana = 100;
}
currentMana = validMana;
activeSpellDeck.currentMana = validMana;
manaWasCorrupted = true;
console.log('✅ Mana synced to:', validMana);
}
// CRÍTICO: Actualizar UI si hubo corrupción
if (manaWasCorrupted) {
console.log('🔧 Mana corruption detected and fixed');
// Mana UI updates handled by deck menu system
console.log('✅ Mana system repaired - currentMana:', currentMana, 'activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
} else {
console.log('✅ Mana system healthy - no corruption detected');
}
console.log('=== MANA VALIDATION COMPLETE ===');
return !manaWasCorrupted;
}
// Simplified spell visual effects system
var SpellVisualEffects = {
// Simplified pre-cast effects - basic flash only
createPreCastEffects: function createPreCastEffects(spellType, wizard) {
// Basic flash effect for all spells
LK.effects.flashObject(wizard, this.getSpellColor(spellType), 300);
},
// Get spell color for basic effects
getSpellColor: function getSpellColor(spellType) {
if (spellType === 'fireball') return 0xFF4500;
if (spellType === 'lightning') return 0x00FFFF;
if (spellType === 'heal') return 0x00FF88;
return 0xFFFFFF;
},
// Simplified casting aura - single flash effect
createCastingAura: function createCastingAura(wizard, color) {
LK.effects.flashObject(wizard, color, 500);
},
// Simplified spell ring - basic visual feedback
createSpellRing: function createSpellRing(wizard, color) {
// Simple screen flash instead of complex ring
LK.effects.flashScreen(color, 200);
}
};
// Unified spell configuration system
var spellConfigs = {
fireball: {
manaCost: 30,
damage: 150,
color: 0xFF4500,
sound: 'fireWhoosh',
effect: 'projectile',
targetType: 'enemy',
description: 'Daño: 150 al enemigo más cercano'
},
lightning: {
manaCost: 40,
damage: 200,
color: 0x00FFFF,
sound: 'iceFreeze',
effect: 'chain',
targetType: 'enemy',
maxTargets: 3,
description: 'Cadena de rayos entre enemigos'
},
heal: {
manaCost: 25,
healing: 50,
color: 0x00FF00,
sound: 'spellCast',
effect: 'heal',
targetType: 'self',
description: 'Restaura puntos de salud'
}
};
// Unified spell casting validation
function _canCastSpell(spellId) {
validateManaSystem();
if (!spellId) {
console.log('_canCastSpell: No spell ID provided');
return false;
}
var config = spellConfigs[spellId];
if (!config) {
console.log('_canCastSpell: Spell config not found for ID:', spellId);
return false;
}
// Check mana cost
if (currentMana < config.manaCost) {
console.log('_canCastSpell: Not enough mana. Required:', config.manaCost, 'Current:', currentMana);
return false;
}
// Check cooldown
var currentTick = LK.ticks || 0;
if (cardCooldowns[spellId] && currentTick < cardCooldowns[spellId]) {
console.log('_canCastSpell: Card on cooldown:', spellId);
return false;
}
console.log('_canCastSpell: Spell can be cast:', spellId);
return true;
}
// Unified spell casting function
function castSpell(spellId) {
console.log('=== CASTING SPELL:', spellId, '===');
console.log('Current mana before cast:', currentMana);
var config = spellConfigs[spellId];
if (!config) {
console.log('Spell config not found:', spellId);
return false;
}
// Basic casting effects
LK.effects.flashObject(wizard, config.color, 300);
LK.effects.flashScreen(config.color, 200);
var success = false;
var result = '';
// Execute spell based on effect type
if (config.effect === 'projectile') {
success = executeProjectileSpell(config);
result = config.description;
} else if (config.effect === 'chain') {
var chainResult = executeChainSpell(config);
success = chainResult.success;
result = 'Cadena de ' + chainResult.targets + ' rayos - Daño: ' + config.damage;
} else if (config.effect === 'heal') {
var healResult = executeHealSpell(config);
success = healResult.success;
result = healResult.actualHealing > 0 ? 'Curado: ' + healResult.actualHealing + ' HP' : 'Salud completa';
}
if (success) {
// Consume mana and set cooldown
currentMana = Math.max(0, currentMana - config.manaCost);
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
updateManaDisplay();
cardCooldowns[spellId] = LK.ticks + cardCooldownDuration;
LK.getSound(config.sound).play();
showSpellDescription(spellId.toUpperCase(), result, config.color);
}
return success;
}
// Projectile spell execution (fireball)
function executeProjectileSpell(config) {
var allEnemies = collisionArrayPool.getAllEnemies();
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy.isDying) continue;
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
if (closestEnemy) {
// Create projectile trail
var simpleTrail = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 1.5,
scaleY: 1.5
}));
simpleTrail.tint = config.color;
simpleTrail.alpha = 0.7;
simpleTrail.zIndex = 1610;
// Animate trail
tween(simpleTrail, {
x: closestEnemy.x,
y: closestEnemy.y,
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (simpleTrail.parent) simpleTrail.destroy();
}
});
// Create projectile
var projectile = ProjectileFactory.createProjectile('fireBall', wizard.x, wizard.y, closestEnemy.x, closestEnemy.y, {
targetEnemy: closestEnemy,
damage: config.damage
});
return true;
}
return false;
}
// Chain spell execution (lightning)
function executeChainSpell(config) {
var allEnemies = collisionArrayPool.getAllEnemies();
var livingEnemies = [];
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
if (!enemy.isDying && enemy.health > 0 && enemy.parent) {
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
enemy.distanceFromWizard = Math.sqrt(dx * dx + dy * dy);
livingEnemies.push(enemy);
}
}
// Sort by distance
livingEnemies.sort(function (a, b) {
return a.distanceFromWizard - b.distanceFromWizard;
});
var targetsHit = Math.min(config.maxTargets, livingEnemies.length);
if (targetsHit > 0) {
// Apply damage and effects
for (var i = 0; i < targetsHit; i++) {
var enemy = livingEnemies[i];
if (enemy && enemy.parent && !enemy.isDying) {
enemy.health -= config.damage;
LK.effects.flashObject(enemy, config.color, 600);
if (enemy.health <= 0) enemy.die();
// Create impact effect
var impact = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 3,
scaleY: 3
}));
impact.tint = config.color;
impact.alpha = 1.0;
tween(impact, {
scaleX: 7,
scaleY: 7,
alpha: 0,
rotation: Math.PI * 3
}, {
duration: 800,
delay: i * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (impact.parent) impact.destroy();
}
});
}
}
return {
success: true,
targets: targetsHit
};
}
return {
success: false,
targets: 0
};
}
// Heal spell execution
function executeHealSpell(config) {
var healthBefore = wizard.health;
wizard.health = Math.min(wizard.health + config.healing, wizard.maxHealth);
var actualHealing = wizard.health - healthBefore;
updateHealthBar();
// Create floating heal text
var healText = new Text2('+' + actualHealing + ' HP', {
size: 120,
fill: config.color,
font: "monospace"
});
healText.anchor.set(0.5, 0.5);
healText.x = wizard.x;
healText.y = wizard.y - 150;
game.addChild(healText);
tween(healText, {
y: healText.y - 300,
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 2500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (healText.parent) healText.destroy();
}
});
return {
success: true,
actualHealing: actualHealing
};
}
// Legacy compatibility functions
function castFireball() {
return castSpell('fireball');
}
function castLightning() {
return castSpell('lightning');
}
function castHeal() {
return castSpell('heal');
}
function _getSpell(spellId) {
for (var i = 0; i < availableSpells.length; i++) {
if (availableSpells[i].id === spellId) {
return availableSpells[i];
}
}
return null;
}
function _getRarityColor(rarity) {
return rarity === 'rare' ? 0x0080FF : 0xFFFFFF;
}
var GameManager = {
updateEntity: function updateEntity(entity) {
if (entity && entity.update && typeof entity.update === 'function') {
entity.update();
}
},
createDamageText: function createDamageText(x, y, damage) {
var damageText = new Text2('-' + damage, {
size: 120,
fill: 0xFF4444,
font: "monospace"
});
damageText.anchor.set(0.5, 0.5);
damageText.x = x;
damageText.y = y - 40;
game.addChild(damageText);
tween(damageText, {
y: y - 120,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
damageText.destroy();
}
});
},
createFlashEffect: function createFlashEffect(target, color, duration) {
LK.effects.flashObject(target, color, duration);
},
createObject: function createObject(type, config) {
if (type === 'coin') return new Coin();
if (type === 'enemy') return new Enemy(config);
if (type === 'projectile') return new Projectile(config);
return null;
},
createVisualEffect: function createVisualEffect(type, target, config) {
// Simple visual effect creation
if (type === 'explosion') {
var explosion = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: target.x,
y: target.y,
scaleX: config.explosionScale || 2,
scaleY: config.explosionScale || 2
}));
explosion.tint = config.explosionColor || 0xFFFFFF;
explosion.alpha = 0.8;
tween(explosion, {
scaleX: (config.explosionScale || 2) * 1.5,
scaleY: (config.explosionScale || 2) * 1.5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
}
};
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
// Usar valores directos en lugar de configuraciones
// Usar valores directos en lugar de configuraciones complejas
// Usar valores directos en lugar de configuraciones complejas
// Usar valores directos en lugar de configuraciones complejas
// Usar valores directos en lugar de configuraciones complejas
/****
* Global State Management
****/
// Simplified global state - no gameState object needed
var gameStarted = false;
var selectedEnemy = null;
var coinCounter = 0;
var enemyKillCounter = 0;
var pathLastSpawnTime = [-1, -1, -1, -1, -1];
var pathConsecutiveSpawns = [0, 0, 0, 0, 0];
var lastSpawnedPath = -1;
/****
* Global Systems
****/
// Unified game management system
var gameManager = GameManager;
// Simple Collision Array Pool
var collisionArrayPool = {
allEnemies: [],
clearArray: function clearArray(arrayName) {
if (this[arrayName]) {
this[arrayName].length = 0;
}
return this[arrayName];
},
getAllEnemies: function getAllEnemies() {
var array = this.clearArray('allEnemies');
for (var i = 0; i < enemies.length; i++) {
array.push(enemies[i]);
}
for (var i = 0; i < ogres.length; i++) {
array.push(ogres[i]);
}
for (var i = 0; i < knights.length; i++) {
array.push(knights[i]);
}
for (var i = 0; i < miniBosses.length; i++) {
array.push(miniBosses[i]);
}
return array;
}
};
/****
* Unified Projectile Manager
****/
var ProjectileFactory = {
// Simplified projectile creation - all types use same Projectile class
createProjectile: function createProjectile(type, startX, startY, targetX, targetY, config) {
var projectile = new Projectile(type);
projectile.x = startX;
projectile.y = startY;
if (config) {
for (var key in config) projectile[key] = config[key];
}
if (targetX !== undefined && targetY !== undefined) {
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
}
}
game.addChild(projectile);
projectiles.push(projectile);
return projectile;
},
createBasicAttack: function createBasicAttack(wizard, enemy) {
return this.createProjectile('projectile', wizard.x, wizard.y, enemy.x, enemy.y, {
targetEnemy: enemy,
damage: 100
});
},
createSpellProjectile: function createSpellProjectile(spellType, wizard, targetX, targetY) {
return this.createProjectile(spellType, wizard.x, wizard.y, targetX, targetY, {
damage: 150
});
},
removeProjectile: function removeProjectile(projectile) {
for (var i = projectiles.length - 1; i >= 0; i--) {
if (projectiles[i] === projectile) {
projectiles.splice(i, 1);
break;
}
}
if (projectile.parent) projectile.destroy();
}
};
// Removed globalDamageHandler and globalEffectManager aliases - use GameManager directly
/****
* Game Objects (Legacy compatibility)
****/
// Direct global arrays - no gameState needed
var enemies = [];
var ogres = [];
var knights = [];
var miniBosses = [];
var coins = [];
var projectiles = [];
// Single game menu object
var gameMenu;
// Create tutorial system first (initially hidden)
var tutorial = game.addChild(new Tutorial());
tutorial.visible = false;
// Create and show game menu
gameMenu = game.addChild(new GameMenu());
// Create toggle button for in-game card panel
var cardToggleButton = LK.getAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
LK.gui.topRight.addChild(cardToggleButton);
cardToggleButton.x = -80;
cardToggleButton.y = 80;
cardToggleButton.tint = 0x4a0e4e;
cardToggleButton.visible = false;
var cardToggleText = new Text2('CARTAS', {
size: 35,
fill: 0xFFFFFF,
font: "monospace"
});
cardToggleText.anchor.set(0.5, 0.5);
cardToggleText.x = -80;
cardToggleText.y = 80;
LK.gui.topRight.addChild(cardToggleText);
cardToggleText.visible = false;
// Add interaction to toggle button
cardToggleButton.down = function (x, y, obj) {
// Visual feedback for button press
LK.effects.flashObject(obj, 0x4169E1, 200);
tween(obj, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
toggleCardPanel();
};
// Simple spell system variables
var currentDeck = storage.spellDeck || ['fireball', 'heal', 'lightning'];
var maxMana = 100;
var currentMana = 100; // Starting mana
var manaRegenRate = 1; // Mana regenerated per second (60 ticks)
var manaRegenTimer = 0;
var spellCooldowns = {};
var cardCooldowns = {}; // Track cooldown for each card
var cardCooldownDuration = 300; // 5 seconds at 60fps
storage.spellDeck = currentDeck.slice();
// Basic system validation
console.log('=== SYSTEM VALIDATION ===');
console.log('currentDeck:', currentDeck);
console.log('currentMana:', currentMana);
console.log('maxMana:', maxMana);
console.log('Tween system available:', validateTweenAvailability());
var activeSpellDeck = {
currentDeck: currentDeck.slice(),
currentMana: maxMana,
maxMana: maxMana,
availableSpells: availableSpells,
getSpell: function getSpell(spellId) {
return _getSpell(spellId);
},
canCastSpell: function canCastSpell(spellId) {
return _canCastSpell(spellId);
},
castSpell: function castSpell(spellId, targetX, targetY) {
console.log('=== CASTING SPELL FROM ACTIVE DECK ===');
console.log('Spell ID:', spellId);
return castSpell(spellId);
},
getRarityColor: function getRarityColor(rarity) {
return _getRarityColor(rarity);
}
};
// Simple tween validation at startup
if (validateTweenAvailability()) {
console.log('Tween system ready');
} else {
console.warn('Tween system using fallback');
}
// PASO 1: Verificar activeSpellDeck después de su creación
console.log('activeSpellDeck created successfully:');
console.log('- currentDeck:', activeSpellDeck.currentDeck);
console.log('- currentMana:', activeSpellDeck.currentMana);
console.log('- maxMana:', activeSpellDeck.maxMana);
console.log('- availableSpells count:', activeSpellDeck.availableSpells.length);
// Initialize spell unlocking system
var lastUnlockCheck = 0;
function checkSpellUnlocks() {
if (!gameMenu.spellDeck) {
gameMenu.spellDeck = new SpellDeck();
}
// Only check unlocks when kill counter changes
if (enemyKillCounter === lastUnlockCheck) return;
lastUnlockCheck = enemyKillCounter;
// Unlock spells based on achievements with messages
if (enemyKillCounter >= 10 && !storage.lightningUnlocked) {
storage.lightningUnlocked = true;
gameMenu.spellDeck.unlockSpell('lightning');
LK.effects.flashScreen(0x00FFFF, 500);
showSpellUnlockMessage('LIGHTNING', 'Cadena de rayos entre enemigos');
}
if (enemyKillCounter >= 25 && !storage.shieldUnlocked) {
storage.shieldUnlocked = true;
gameMenu.spellDeck.unlockSpell('shield');
LK.effects.flashScreen(0x0080FF, 500);
showSpellUnlockMessage('MAGIC SHIELD', 'Inmunidad temporal al daño');
}
if (enemyKillCounter >= 50 && !storage.teleportUnlocked) {
storage.teleportUnlocked = true;
gameMenu.spellDeck.unlockSpell('teleport');
LK.effects.flashScreen(0x8000FF, 500);
showSpellUnlockMessage('TELEPORT', 'Mueve instantáneamente al mago');
}
if (enemyKillCounter >= 75 && !storage.timeSlowUnlocked) {
storage.timeSlowUnlocked = true;
gameMenu.spellDeck.unlockSpell('timeSlow');
LK.effects.flashScreen(0xFF8000, 500);
showSpellUnlockMessage('TIME SLOW', 'Ralentiza todos los enemigos');
}
if (enemyKillCounter >= 100 && !storage.meteorUnlocked) {
storage.meteorUnlocked = true;
gameMenu.spellDeck.unlockSpell('meteor');
LK.effects.flashScreen(0xFF0000, 500);
showSpellUnlockMessage('METEOR', 'Daño masivo en área');
}
}
function showSpellUnlockMessage(spellName, description) {
var unlockText = new Text2('NUEVO HECHIZO DESBLOQUEADO!\n' + spellName + '\n' + description, {
size: 60,
fill: 0xFFD700,
font: "monospace"
});
unlockText.anchor.set(0.5, 0.5);
unlockText.x = 2048 / 2;
unlockText.y = 2732 / 2;
game.addChild(unlockText);
// Animate unlock message
tween(unlockText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unlockText, {
alpha: 0,
y: unlockText.y - 200
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (unlockText.parent) unlockText.destroy();
}
});
}
});
}
// Function to show spell description when cast
function showSpellDescription(spellName, description, color) {
var descText = new Text2(spellName + '\n' + description, {
size: 50,
fill: color,
font: "monospace"
});
descText.anchor.set(0.5, 0.5);
descText.x = wizard.x;
descText.y = wizard.y - 200;
game.addChild(descText);
// Magical sparkles removed for simplification
// Animate description
tween(descText, {
y: descText.y - 80,
alpha: 0
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (descText.parent) descText.destroy();
}
});
}
// Create unified path system - all 5 paths at once
var paths = [];
var knightX = 2048 / 2;
var knightY = 2732 - 250;
var pathAngles = [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6];
var wizardX = knightX;
var wizardY = 2732 - 600;
// Unified path creation function
function createUnifiedPaths() {
var spawnPositions = [{
x: 2048 / 2,
y: -100
}, {
x: 2048 + 50,
y: -50
}, {
x: -50,
y: -50
}, {
x: -100,
y: 2732 / 2 + 400
}, {
x: 2048 + 100,
y: 2732 / 2 + 400
}];
var pathAngles = [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6];
for (var p = 0; p < 5; p++) {
var angle = pathAngles[p];
var spawnPos = spawnPositions[p];
var actualPathLength = Math.sqrt((spawnPos.x - wizardX) * (spawnPos.x - wizardX) + (spawnPos.y - wizardY) * (spawnPos.y - wizardY));
// Create stone segments
var segmentSize = 80;
var numSegments = Math.floor(actualPathLength / segmentSize);
for (var s = 0; s < numSegments; s++) {
var segmentDistance = s * segmentSize + segmentSize / 2;
var segmentX = spawnPos.x - Math.cos(angle) * segmentDistance;
var segmentY = spawnPos.y - Math.sin(angle) * segmentDistance;
if (segmentX >= -100 && segmentX <= 2148 && segmentY >= -100 && segmentY <= 2832) {
var stoneSegment = game.addChild(LK.getAsset('stonePath', {
anchorX: 0.5,
anchorY: 0.5,
x: segmentX,
y: segmentY,
scaleX: 2.0,
scaleY: 2.0,
rotation: angle + Math.PI / 2
}));
stoneSegment.alpha = 0;
stoneSegment.visible = false;
stoneSegment.pathIndex = p;
}
}
// Create collision area
var centerX = (spawnPos.x + wizardX) / 2;
var centerY = (spawnPos.y + wizardY) / 2;
var path = game.addChild(LK.getAsset('pathSelector', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
scaleX: 4,
scaleY: actualPathLength / 60,
rotation: angle + Math.PI / 2
}));
path.alpha = 0;
path.visible = false;
path.pathIndex = p;
// Add path number
var pathNumber = new Text2((p + 1).toString(), {
size: 120,
fill: 0xFFD700,
font: "monospace"
});
pathNumber.anchor.set(0.5, 0.5);
pathNumber.x = spawnPos.x;
pathNumber.y = spawnPos.y - 80;
pathNumber.visible = false;
pathNumber.pathIndex = p;
game.addChild(pathNumber);
// Add touch handler
path.down = function (x, y, obj) {
wizard.attack(obj.pathIndex);
};
paths.push(path);
}
}
// Create all paths
createUnifiedPaths();
// Pixel art scaling handled by engine automatically
// Set fondodelacueva as the actual game background
var backgroundMap = game.addChild(LK.getAsset('fondodelacueva', {
anchorX: 0,
anchorY: 0,
scaleX: 19.5,
scaleY: 26.0,
x: 0,
y: 0
}));
// Send background to the back but use a less extreme z-index
backgroundMap.zIndex = -100;
// Hide background initially during menu
backgroundMap.visible = false;
backgroundMap.alpha = 1.0;
// Create wizard early to ensure it's available for other classes
var wizard = game.addChild(new Wizard());
wizard.x = knightX;
wizard.y = 2732 - 600; // Position wizard higher on screen
wizard.visible = false;
// UI Elements
// Removed scoreText and levelText to eliminate stray characters in top right
var coinCounter = 0;
var enemyKillCounter = 0;
var coinText = new Text2('Coins: 0', {
size: 60,
fill: 0xFFD700,
font: "monospace"
});
coinText.anchor.set(0, 0);
LK.gui.topLeft.addChild(coinText);
coinText.x = 120;
coinText.y = 90;
coinText.visible = false;
var killCountText = new Text2('Puntuacion: 0', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
killCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(killCountText);
killCountText.x = 120;
killCountText.y = 50;
killCountText.visible = false;
var tapText = new Text2('TAP ENEMIES TO ATTACK!', {
size: 80,
fill: 0x00FF00,
font: "monospace"
});
tapText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tapText);
tapText.y = -400;
tapText.visible = false;
// Health bar UI
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(healthBarBg);
healthBarBg.x = 120;
healthBarBg.y = 20;
healthBarBg.visible = false;
var healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(healthBar);
healthBar.x = 120;
healthBar.y = 20;
healthBar.visible = false;
var healthText = new Text2('Health: 100/100', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120;
healthText.y = 65;
healthText.visible = false;
// Mana bar UI
var manaBarBg = LK.getAsset('manaBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(manaBarBg);
manaBarBg.x = 120;
manaBarBg.y = 105;
manaBarBg.visible = false;
var manaBar = LK.getAsset('manaBar', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: 1
});
LK.gui.topLeft.addChild(manaBar);
manaBar.x = 120;
manaBar.y = 105;
manaBar.visible = false;
var manaText = new Text2('Mana: 100/100', {
size: 60,
fill: 0x4169E1,
font: "monospace"
});
manaText.anchor.set(0, 0);
LK.gui.topLeft.addChild(manaText);
manaText.x = 120;
manaText.y = 150;
manaText.visible = false;
function updateManaDisplay() {
var manaPercent = currentMana / maxMana;
manaBar.scaleX = manaPercent * 2;
manaText.setText('Mana: ' + Math.floor(currentMana) + '/' + maxMana);
// Sync with activeSpellDeck
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
}
function updateHealthBar() {
var healthPercent = wizard.health / wizard.maxHealth;
healthBar.scaleX = healthPercent;
healthText.setText('Health: ' + wizard.health + '/' + wizard.maxHealth);
// Change color based on health
if (healthPercent > 0.6) {
healthBar.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBar.tint = 0xffff00; // Yellow
} else {
healthBar.tint = 0xff0000; // Red
}
}
// Enemy spawning variables
var enemySpawnTimer = 0;
var lastSpawnedPath = -1; // Track the last spawned path
var consecutiveSpawns = 0; // Track consecutive spawns from same path
// Cooldown system variables
var pathLastSpawnTime = [-1, -1, -1, -1, -1]; // Track last spawn time for each path
var pathConsecutiveSpawns = [0, 0, 0, 0, 0]; // Track consecutive spawns per path
var pathCooldownDuration = 300; // 5 seconds at 60fps
// Unified SpawnManager for streamlined spawn control and enemy lifecycle
var SpawnManager = {
// Consolidated spawn configuration
spawnConfig: {
skeleton: {
interval: 90,
maxCount: 15,
startAt: 0
},
ogre: {
interval: 180,
maxCount: 4,
startAt: 15
},
knight: {
interval: 300,
maxCount: 3,
startAt: 30
},
miniBoss: {
interval: 60,
maxCount: 1,
startAt: 80,
endAt: 85,
chance: 0.02
}
},
// Unified spawn timers
spawnTimers: {
skeleton: 0,
ogre: 0,
knight: 0,
miniBoss: 0
},
// Streamlined spawn validation
canSpawnEnemy: function canSpawnEnemy(type, difficulty, totalEnemies) {
var config = this.spawnConfig[type];
if (!config) return false;
var collection = [];
if (type === 'skeleton') collection = enemies;else if (type === 'ogre') collection = ogres;else if (type === 'knight') collection = knights;else if (type === 'miniBoss') collection = miniBosses;
var timer = this.spawnTimers[type];
// Usar valores directos para thresholds
var threshold = 0;
if (type === 'ogre') threshold = 15;else if (type === 'knight') threshold = 30;else if (type === 'miniBoss') threshold = 80;
// Universal spawn conditions with type-specific rules
var baseConditions = timer >= config.interval && enemyKillCounter >= threshold && collection.length < config.maxCount && totalEnemies < 25;
// Special conditions for specific types
if (type === 'miniBoss') {
return baseConditions && enemyKillCounter <= config.endAt && miniBosses.length === 0 && Math.random() < config.chance;
}
return baseConditions && miniBosses.length === 0;
},
// Unified spawn execution with specific enemy classes
executeSpawn: function executeSpawn(type, difficulty, level) {
var totalEnemies = globalEnemyManager.getAllEnemies().length;
if (!this.canSpawnEnemy(type, difficulty, totalEnemies)) return null;
var enemy = globalEnemyManager.createEnemy(type, difficulty, level);
if (enemy) {
game.addChild(enemy);
// Add directly to global arrays
if (type === 'skeleton') enemies.push(enemy);else if (type === 'ogre') ogres.push(enemy);else if (type === 'knight') knights.push(enemy);else if (type === 'miniBoss') miniBosses.push(enemy);
this.spawnTimers[type] = 0;
// Update path tracking for skeleton spawns
if (type === 'skeleton') {
pathConsecutiveSpawns[enemy.pathIndex]++;
pathLastSpawnTime[enemy.pathIndex] = LK.ticks;
lastSpawnedPath = enemy.pathIndex;
}
}
return enemy;
},
// Streamlined interval updates
updateSpawnIntervals: function updateSpawnIntervals(difficulty, level) {
// Usar valores directos para skeleton
if (difficulty === 'FACIL') {
this.spawnConfig.skeleton.interval = Math.max(60, 120 - level * 5);
this.spawnConfig.ogre.interval = 240;
this.spawnConfig.knight.interval = 400;
} else if (difficulty === 'NORMAL') {
this.spawnConfig.skeleton.interval = Math.max(40, 90 - level * 6);
this.spawnConfig.ogre.interval = 180;
this.spawnConfig.knight.interval = 300;
} else {
// DIFICIL
this.spawnConfig.skeleton.interval = Math.max(20, 60 - level * 4);
this.spawnConfig.ogre.interval = 120;
this.spawnConfig.knight.interval = 200;
}
},
// Unified timer updates
updateTimers: function updateTimers() {
for (var type in this.spawnTimers) {
this.spawnTimers[type]++;
}
// Check if all paths are in cooldown and reset if needed
var allPathsInCooldown = true;
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
if (pathConsecutiveSpawns[pathIdx] < 2) {
allPathsInCooldown = false;
break;
}
}
if (allPathsInCooldown) {
// Reset all path cooldowns to prevent spawn deadlock
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
pathConsecutiveSpawns[pathIdx] = 0;
pathLastSpawnTime[pathIdx] = -1;
}
}
},
// Streamlined spawn cycle for all enemy types
processSpawnCycle: function processSpawnCycle(difficulty, level) {
this.updateSpawnIntervals(difficulty, level);
this.updateTimers();
// Clean up any null/destroyed enemies from collections first
this.cleanupDestroyedEnemies();
var enemyTypes = ['skeleton', 'ogre', 'knight', 'miniBoss'];
for (var i = 0; i < enemyTypes.length; i++) {
this.executeSpawn(enemyTypes[i], difficulty, level);
}
},
// Add cleanup method for destroyed enemies
cleanupDestroyedEnemies: function cleanupDestroyedEnemies() {
// Clean up global arrays directly
var allArrays = [enemies, ogres, knights, miniBosses];
for (var arrayIdx = 0; arrayIdx < allArrays.length; arrayIdx++) {
var array = allArrays[arrayIdx];
for (var i = array.length - 1; i >= 0; i--) {
if (!array[i] || !array[i].parent || array[i].isDying) {
array.splice(i, 1);
}
}
}
}
};
// Game input handling with spell casting support
game.down = function (x, y, obj) {
// Removed automatic fireball casting - spells are now manual only through spell slots
// Tap-to-attack is now handled directly by individual enemies
// No need for path-based attacks since enemies handle their own tap events
};
// Initialize unified management systems for streamlined game architecture
var globalEnemyManager = new EnemyManager();
var globalDeathHandler = new UnifiedDeathHandler();
// Unified death animation function for all enemy types
function createEnemyDeathAnimation(enemy, enemyType, enemyArray) {
globalDeathHandler.executeEnemyDeath(enemy, enemyArray);
}
// In-game spell card panel system
var inGameCardPanel = null;
var cardPanelVisible = false;
var cardPanelElements = [];
// Create in-game card panel
function createInGameCardPanel() {
if (inGameCardPanel) return inGameCardPanel;
// Create semi-transparent background panel
inGameCardPanel = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732,
scaleX: 20,
scaleY: 4
}));
inGameCardPanel.tint = 0x1a0a2e;
inGameCardPanel.alpha = 0.9;
inGameCardPanel.zIndex = 1500;
inGameCardPanel.visible = false;
return inGameCardPanel;
}
// Toggle card panel visibility
function toggleCardPanel() {
if (!gameStarted) return;
cardPanelVisible = !cardPanelVisible;
if (cardPanelVisible) {
showInGameCardPanel();
} else {
hideInGameCardPanel();
}
}
// Show in-game card panel
function showInGameCardPanel() {
if (!inGameCardPanel) createInGameCardPanel();
// Clear existing card elements
clearCardPanelElements();
inGameCardPanel.visible = true;
// Get current deck from activeSpellDeck
var currentDeck = activeSpellDeck ? activeSpellDeck.currentDeck : ['fireball', 'heal', 'lightning'];
// Create cards for current deck
for (var i = 0; i < currentDeck.length && i < 5; i++) {
var spellId = currentDeck[i];
var spell = _getSpell(spellId);
if (!spell) continue;
var cardX = 300 + i * 300;
var cardY = 2732 - 150;
// Create card background
var cardBg = game.addChild(LK.getAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.5,
scaleY: 3.0
}));
cardBg.tint = _getRarityColor(spell.rarity);
cardBg.alpha = 0.9;
cardBg.zIndex = 1501;
cardBg.spellId = spellId;
cardPanelElements.push(cardBg);
// Enhanced ready-to-cast visual feedback with glow effects for in-game panel
if (!isOnCooldown && _canCastSpell(spellId)) {
// Create enhanced magical glow border around ready cards
var inGameGlowBorder = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.2,
scaleY: 3.8
}));
inGameGlowBorder.tint = 0x00FF88;
inGameGlowBorder.alpha = 0.5;
inGameGlowBorder.zIndex = 1501;
cardPanelElements.push(inGameGlowBorder);
// Sparkle effects removed for simplification
// Enhanced ready-to-cast card pulsing animation
tween(cardBg, {
alpha: 1.0,
scaleX: 2.7,
scaleY: 3.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cardBg, {
alpha: 0.9,
scaleX: 2.5,
scaleY: 3.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
// Animate glow border with enhanced breathing effect
tween(inGameGlowBorder, {
alpha: 0.7,
scaleX: 3.6,
scaleY: 4.2
}, {
duration: 1300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(inGameGlowBorder, {
alpha: 0.5,
scaleX: 3.2,
scaleY: 3.8
}, {
duration: 1300,
easing: tween.easeInOut
});
}
});
}
// Check if spell can be cast (mana + cooldown validation)
var canCast = _canCastSpell(spellId);
var currentTick = LK.ticks || 0;
var isOnCooldown = cardCooldowns[spellId] && currentTick < cardCooldowns[spellId];
var hasEnoughMana = currentMana >= (spell.manaCost || 0);
// Enhanced card status visual feedback system
if (isOnCooldown) {
// Create enhanced cooldown overlay with animated elements
var cooldownOverlay = game.addChild(LK.getAsset('cooldownOverlay', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.5,
scaleY: 3.0
}));
cooldownOverlay.alpha = 0.8;
cooldownOverlay.zIndex = 1502;
cardPanelElements.push(cooldownOverlay);
// Create rotating cooldown progress ring
var cooldownRing = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 2.8,
scaleY: 2.8
}));
cooldownRing.tint = 0x4169E1;
cooldownRing.alpha = 0.6;
cooldownRing.zIndex = 1502;
cardPanelElements.push(cooldownRing);
// Animate cooldown ring rotation
tween(cooldownRing, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear
});
// Show enhanced cooldown time remaining with countdown animation
var timeRemaining = Math.ceil((cardCooldowns[spellId] - currentTick) / 60);
var cooldownText = new Text2(timeRemaining.toString(), {
size: 50,
fill: 0xFFFFFF,
font: "monospace"
});
cooldownText.anchor.set(0.5, 0.5);
cooldownText.x = cardX;
cooldownText.y = cardY;
cooldownText.zIndex = 1503;
game.addChild(cooldownText);
cardPanelElements.push(cooldownText);
// Pulsing countdown animation
tween(cooldownText, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cooldownText, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
// Disable card interaction when on cooldown
cardBg.alpha = 0.4;
cardBg.tint = 0x666666; // Gray out on cooldown
} else if (!hasEnoughMana) {
// Enhanced insufficient mana feedback with warning indicators
cardBg.alpha = 0.5;
cardBg.tint = 0xFF4444; // Red tint for insufficient mana
// Create mana warning icon
var manaWarning = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX + 70,
y: cardY - 70,
scaleX: 1.2,
scaleY: 1.2
}));
manaWarning.tint = 0xFF0000;
manaWarning.alpha = 0.9;
manaWarning.zIndex = 1502;
cardPanelElements.push(manaWarning);
// Add warning symbol
var warningText = new Text2('!', {
size: 60,
fill: 0xFFFFFF,
font: "monospace"
});
warningText.anchor.set(0.5, 0.5);
warningText.x = cardX + 70;
warningText.y = cardY - 70;
warningText.zIndex = 1503;
game.addChild(warningText);
cardPanelElements.push(warningText);
// Urgent warning animation for insufficient mana
tween(manaWarning, {
scaleX: 1.6,
scaleY: 1.6,
alpha: 0.5
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(manaWarning, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.9
}, {
duration: 400,
easing: tween.easeIn
});
}
});
} else if (canCast) {
// Simple ready to cast - no animations
cardBg.alpha = 0.9;
cardBg.tint = _getRarityColor(spell.rarity);
} else {
// Default disabled state
cardBg.alpha = 0.6;
cardBg.tint = 0x888888; // Gray tint for disabled
}
// Enhanced card interaction with spell casting
cardBg.down = function (x, y, obj) {
if (obj.spellId) {
// Enhanced visual feedback
LK.effects.flashObject(obj, 0x00FFFF, 200);
// Scale animation for touch feedback
tween(obj, {
scaleX: obj.scaleX * 1.1,
scaleY: obj.scaleY * 1.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: obj.scaleX / 1.1,
scaleY: obj.scaleY / 1.1
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Cast the spell
castSpellFromCard(obj.spellId);
}
};
// Create card name text
var nameText = new Text2(spell.name, {
size: 30,
fill: 0xFFFFFF,
font: "monospace"
});
nameText.anchor.set(0.5, 0.5);
nameText.x = cardX;
nameText.y = cardY - 40;
nameText.zIndex = 1502;
game.addChild(nameText);
cardPanelElements.push(nameText);
// Create mana cost indicator
if (spell.manaCost) {
var manaText = new Text2(spell.manaCost.toString(), {
size: 40,
fill: 0x4169E1,
font: "monospace"
});
manaText.anchor.set(0.5, 0.5);
manaText.x = cardX + 50;
manaText.y = cardY - 80;
manaText.zIndex = 1502;
game.addChild(manaText);
cardPanelElements.push(manaText);
}
// Create damage/healing indicator
var effectText = '';
if (spell.damage) effectText = 'DMG: ' + spell.damage;
if (spell.healing) effectText = 'HEAL: ' + spell.healing;
if (effectText) {
var effectLabel = new Text2(effectText, {
size: 25,
fill: 0xFFD700,
font: "monospace"
});
effectLabel.anchor.set(0.5, 0.5);
effectLabel.x = cardX;
effectLabel.y = cardY + 40;
effectLabel.zIndex = 1502;
game.addChild(effectLabel);
cardPanelElements.push(effectLabel);
}
}
// Create toggle button to hide panel
var hideButton = game.addChild(LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 2732 - 300,
scaleX: 1.5,
scaleY: 1.5
}));
hideButton.tint = 0xFF4444;
hideButton.zIndex = 1502;
hideButton.down = function (x, y, obj) {
// Visual feedback for hide button
LK.effects.flashObject(obj, 0xFF6666, 200);
tween(obj, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(obj, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeIn
});
}
});
toggleCardPanel();
};
cardPanelElements.push(hideButton);
var hideText = new Text2('CERRAR', {
size: 40,
fill: 0xFFFFFF,
font: "monospace"
});
hideText.anchor.set(0.5, 0.5);
hideText.x = 2048 - 100;
hideText.y = 2732 - 300;
hideText.zIndex = 1503;
game.addChild(hideText);
cardPanelElements.push(hideText);
}
// Hide in-game card panel
function hideInGameCardPanel() {
if (inGameCardPanel) {
inGameCardPanel.visible = false;
}
clearCardPanelElements();
}
// Clear card panel elements
function clearCardPanelElements() {
for (var i = 0; i < cardPanelElements.length; i++) {
if (cardPanelElements[i] && cardPanelElements[i].parent) {
cardPanelElements[i].destroy();
}
}
cardPanelElements = [];
}
// Targeting system variables
var targetingMode = false;
var targetingSpell = null;
var targetingCursor = null;
var targetingRange = null;
// Create targeting cursor
function createTargetingCursor() {
if (targetingCursor) return targetingCursor;
targetingCursor = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
}));
targetingCursor.tint = 0x00FFFF;
targetingCursor.alpha = 0.8;
targetingCursor.zIndex = 2000;
targetingCursor.visible = false;
// Add orbiting particles around cursor for enhanced feedback
targetingCursor.particles = [];
for (var p = 0; p < 4; p++) {
var particle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
}));
particle.tint = 0x00FFFF;
particle.alpha = 0.6;
particle.zIndex = 1999;
particle.visible = false;
particle.orbitAngle = p * Math.PI * 2 / 4;
targetingCursor.particles.push(particle);
}
return targetingCursor;
}
// Create targeting range indicator
function createTargetingRange() {
if (targetingRange) return targetingRange;
targetingRange = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 8.0
}));
targetingRange.tint = 0x00FF00;
targetingRange.alpha = 0.3;
targetingRange.zIndex = 1999;
targetingRange.visible = false;
return targetingRange;
}
// Enter targeting mode
function enterTargetingMode(spellId) {
targetingMode = true;
targetingSpell = spellId;
// Create targeting visuals
createTargetingCursor();
createTargetingRange();
targetingCursor.visible = true;
targetingRange.visible = true;
// Position range indicator around wizard
targetingRange.x = wizard.x;
targetingRange.y = wizard.y;
// Add pulsing animation to cursor
tween(targetingCursor, {
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetingCursor && targetingCursor.visible) {
tween(targetingCursor, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
}
});
// Show targeting instructions
showTargetingInstructions(spellId);
}
// Exit targeting mode
function exitTargetingMode() {
targetingMode = false;
targetingSpell = null;
if (targetingCursor) {
targetingCursor.visible = false;
}
if (targetingRange) {
targetingRange.visible = false;
}
// Hide card panel after targeting
hideInGameCardPanel();
cardPanelVisible = false;
}
// Show targeting instructions
function showTargetingInstructions(spellId) {
var spell = _getSpell(spellId);
var instructionText = '';
if (spellId === 'fireball') {
instructionText = 'TOCA UN ENEMIGO PARA LANZAR FIREBALL';
} else if (spellId === 'lightning') {
instructionText = 'TOCA UN ENEMIGO PARA CADENA DE RAYOS';
} else if (spellId === 'heal') {
instructionText = 'TOCA PARA CURARTE';
} else {
instructionText = 'SELECCIONA OBJETIVO PARA ' + (spell ? spell.name : 'HECHIZO');
}
var targetingInstructions = new Text2(instructionText, {
size: 50,
fill: 0x00FFFF,
font: "monospace"
});
targetingInstructions.anchor.set(0.5, 0.5);
targetingInstructions.x = 2048 / 2;
targetingInstructions.y = 400;
targetingInstructions.zIndex = 2001;
game.addChild(targetingInstructions);
// Auto-remove instructions after 3 seconds
tween(targetingInstructions, {
alpha: 0
}, {
duration: 3000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (targetingInstructions.parent) targetingInstructions.destroy();
}
});
}
// Execute spell at target location
function executeSpellAtTarget(spellId, targetX, targetY, targetEnemy) {
console.log('Executing spell', spellId, 'at', targetX, targetY);
var success = false;
if (spellId === 'fireball') {
if (targetEnemy) {
// Create targeted fireball
var fireball = ProjectileFactory.createProjectile('fireBall', wizard.x, wizard.y, targetEnemy.x, targetEnemy.y, {
targetEnemy: targetEnemy,
damage: 150
});
LK.getSound('fireWhoosh').play();
showSpellDescription('FIREBALL', 'Daño: 150 dirigido', 0xFF4500);
success = true;
}
} else if (spellId === 'lightning') {
if (targetEnemy) {
// Execute lightning with target as starting point
LK.effects.flashScreen(0x00FFFF, 800);
LK.getSound('iceFreeze').play();
var allEnemies = collisionArrayPool.getAllEnemies();
var livingEnemies = [];
// Start chain from targeted enemy
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
if (!enemy.isDying && enemy.health > 0 && enemy.parent) {
var dx = enemy.x - targetEnemy.x;
var dy = enemy.y - targetEnemy.y;
enemy.distanceFromTarget = Math.sqrt(dx * dx + dy * dy);
livingEnemies.push(enemy);
}
}
// Sort by distance from target
livingEnemies.sort(function (a, b) {
return a.distanceFromTarget - b.distanceFromTarget;
});
var targetsHit = Math.min(3, livingEnemies.length);
if (targetsHit > 0) {
// Apply lightning damage
for (var i = 0; i < targetsHit; i++) {
var enemy = livingEnemies[i];
if (enemy && enemy.parent && !enemy.isDying) {
enemy.health -= 200;
LK.effects.flashObject(enemy, 0x00FFFF, 600);
if (enemy.health <= 0) enemy.die();
// Create lightning visual
var lightningImpact = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 3,
scaleY: 3
}));
lightningImpact.tint = 0x00FFFF;
lightningImpact.alpha = 1.0;
tween(lightningImpact, {
scaleX: 7,
scaleY: 7,
alpha: 0,
rotation: Math.PI * 3
}, {
duration: 800,
delay: i * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (lightningImpact.parent) lightningImpact.destroy();
}
});
}
}
showSpellDescription('LIGHTNING', 'Cadena dirigida: ' + targetsHit + ' rayos', 0x00FFFF);
}
success = true;
}
} else if (spellId === 'heal') {
// Heal can be cast anywhere, always targets wizard
var healthBefore = wizard.health;
wizard.health = Math.min(wizard.health + 50, wizard.maxHealth);
var actualHealing = wizard.health - healthBefore;
updateHealthBar();
// Enhanced healing effects at target location
LK.effects.flashScreen(0x00FF00, 500);
LK.effects.flashObject(wizard, 0x00FF00, 1000);
// Create healing aura at clicked location
var healingAura = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: targetX,
y: targetY,
scaleX: 4,
scaleY: 4
}));
healingAura.tint = 0x00FF00;
healingAura.alpha = 0.8;
tween(healingAura, {
scaleX: 12,
scaleY: 12,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (healingAura.parent) healingAura.destroy();
}
});
showSpellDescription('HEAL', 'Curado: ' + actualHealing + ' HP', 0x00FF00);
success = true;
}
if (success) {
LK.getSound('spellCast').play();
cardCooldowns[spellId] = LK.ticks + cardCooldownDuration;
}
return success;
}
// Universal spell casting function for all card interfaces
function castSpellFromAnyCard(spellId, sourceInterface) {
console.log('=== CASTING SPELL FROM', sourceInterface, '===');
console.log('Spell ID:', spellId);
console.log('Current mana before cast:', currentMana);
console.log('activeSpellDeck.currentMana before cast:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
// Check if spell can be cast with enhanced validation
if (!_canCastSpell(spellId)) {
console.log('Cannot cast spell:', spellId);
LK.effects.flashScreen(0xFF0000, 200);
return false;
}
// Get spell data for mana consumption
var spell = _getSpell(spellId);
if (!spell) {
console.log('Spell not found for ID:', spellId);
return false;
}
// Execute the spell using unified casting system
console.log('Executing spell cast for:', spellId);
var castSuccess = castSpell(spellId);
if (castSuccess) {
console.log('Spell cast successful from', sourceInterface, ':', spellId);
// Show success message
showSpellDescription(spell.name.toUpperCase(), 'Lanzado desde ' + sourceInterface, spellConfigs[spellId].color);
return true;
} else {
console.log('Unified spell cast failed for:', spellId);
return false;
}
}
// Cast spell from in-game card
function castSpellFromCard(spellId) {
// Use the universal casting function
var castSuccess = castSpellFromAnyCard(spellId, 'CARD_PANEL');
if (castSuccess) {
console.log('Spell cast successful:', spellId);
// Enhanced contextual success feedback based on spell type
if (spellId === 'fireball') {
LK.effects.flashScreen(0xFF4500, 400);
// Create fire burst around wizard
for (var f = 0; f < 6; f++) {
var fireBurst = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 1.5,
scaleY: 1.5
}));
fireBurst.tint = 0xFF6600;
fireBurst.alpha = 0.8;
fireBurst.zIndex = 1700;
var angle = f * Math.PI * 2 / 6;
tween(fireBurst, {
x: wizard.x + Math.cos(angle) * 120,
y: wizard.y + Math.sin(angle) * 120,
alpha: 0,
scaleX: 3.0,
scaleY: 3.0,
rotation: Math.PI * 2
}, {
duration: 600,
delay: f * 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (fireBurst.parent) fireBurst.destroy();
}
});
}
} else if (spellId === 'lightning') {
LK.effects.flashScreen(0x00FFFF, 500);
// Create electric arcs around wizard
for (var l = 0; l < 8; l++) {
var lightningArc = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x + (Math.random() - 0.5) * 200,
y: wizard.y + (Math.random() - 0.5) * 200,
scaleX: 0.8,
scaleY: 3.0
}));
lightningArc.tint = 0x00FFFF;
lightningArc.alpha = 0.9;
lightningArc.rotation = Math.random() * Math.PI * 2;
lightningArc.zIndex = 1700;
tween(lightningArc, {
alpha: 0,
scaleX: 0.3,
scaleY: 4.0
}, {
duration: 300,
delay: l * 50,
easing: tween.easeOut,
onFinish: function onFinish() {
if (lightningArc.parent) lightningArc.destroy();
}
});
}
} else if (spellId === 'heal') {
LK.effects.flashScreen(0x00FF00, 350);
// Create healing aura waves
for (var h = 0; h < 4; h++) {
var healWave = game.addChild(LK.getAsset('spellCardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: wizard.x,
y: wizard.y,
scaleX: 1.0 + h * 0.5,
scaleY: 1.0 + h * 0.5
}));
healWave.tint = 0x00FF88;
healWave.alpha = 0.6 - h * 0.1;
healWave.zIndex = 1700;
tween(healWave, {
scaleX: 8.0 + h * 2,
scaleY: 8.0 + h * 2,
alpha: 0
}, {
duration: 1200 + h * 200,
delay: h * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (healWave.parent) healWave.destroy();
}
});
}
} else {
// Default success feedback
LK.effects.flashScreen(0x00FF00, 300);
}
// Create spell success text with enhanced animation
var successText = new Text2('SPELL CAST!', {
size: 80,
fill: 0xFFD700,
font: "monospace"
});
successText.anchor.set(0.5, 0.5);
successText.x = wizard.x;
successText.y = wizard.y - 180;
successText.zIndex = 1701;
game.addChild(successText);
tween(successText, {
y: successText.y - 100,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
rotation: Math.PI / 8
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (successText.parent) successText.destroy();
}
});
// Hide card panel after successful cast
hideInGameCardPanel();
cardPanelVisible = false;
} else {
console.log('Spell cast failed:', spellId);
// Enhanced failure feedback with shake effect
LK.effects.flashScreen(0xFF0000, 300);
// Create failure warning
var failureText = new Text2('CAST FAILED!', {
size: 60,
fill: 0xFF4444,
font: "monospace"
});
failureText.anchor.set(0.5, 0.5);
failureText.x = wizard.x;
failureText.y = wizard.y - 150;
failureText.zIndex = 1701;
game.addChild(failureText);
// Shake and fade failure text
var originalFailX = failureText.x;
tween(failureText, {
x: originalFailX - 10
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(failureText, {
x: originalFailX + 10
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(failureText, {
x: originalFailX,
alpha: 0,
y: failureText.y - 80
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (failureText.parent) failureText.destroy();
}
});
}
});
}
});
}
}
// Add targeting system to game mouse/touch handling
game.move = function (x, y, obj) {
if (targetingMode && targetingCursor) {
// Update cursor position to follow mouse/touch
targetingCursor.x = x;
targetingCursor.y = y;
// Calculate if target is in range
var dx = x - wizard.x;
var dy = y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxRange = 400; // Maximum spell range
// Change cursor color based on range
if (distance <= maxRange) {
targetingCursor.tint = 0x00FF00; // Green for in range
targetingCursor.alpha = 0.8;
} else {
targetingCursor.tint = 0xFF0000; // Red for out of range
targetingCursor.alpha = 0.5;
}
}
};
game.down = function (x, y, obj) {
// Simple tap to attack handling - no spell casting interference
// All spell casting is now handled through the in-game card panel system
};
// Main game update loop
game.update = function () {
// Sort children by z-index to ensure proper rendering order
game.children.sort(function (a, b) {
return (a.zIndex || 0) - (b.zIndex || 0);
});
// Pause game when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Only update game logic if game has started
if (!gameStarted) {
return;
}
// Change music to epic battle theme at 10 enemies killed
if (enemyKillCounter === 10) {
LK.playMusic('epicBattle', {
volume: 0.8,
fade: {
start: 0,
end: 0.8,
duration: 1500
}
});
}
// Change to mystical ambient music at 25 enemies killed
if (enemyKillCounter === 25) {
LK.playMusic('mysticalAmbient', {
volume: 0.6,
fade: {
start: 0,
end: 0.6,
duration: 2000
}
});
}
// Upgrade menus removed - using spell deck system instead
// Reset consecutive spawns for paths that have cooled down (runs every frame)
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
// Check if enough time has passed since last spawn (cooldown expired)
if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) {
// Reset consecutive spawns for this path due to cooldown
pathConsecutiveSpawns[pathIdx] = 0;
}
}
// Get stored difficulty setting
var selectedDifficulty = storage.difficulty || 'NORMAL';
var difficultyLevel = Math.floor(enemyKillCounter / 10); // Every 10 kills increases difficulty
// Helper functions now integrated into EnemyFactory
// Unified spawn management system
SpawnManager.processSpawnCycle(selectedDifficulty, difficultyLevel);
// Reset path cooldowns for optimized path management
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) {
pathConsecutiveSpawns[pathIdx] = 0;
}
}
// Unified CollisionManager for streamlined collision detection and response
var CollisionManager = {
// Consolidated collision configurations
collisionConfig: {
skeleton: {
damage: 20,
removeOnHit: true
},
ogre: {
damage: 30,
removeOnHit: true
},
knight: {
damage: 40,
removeOnHit: true
},
miniBoss: {
damage: 75,
removeOnHit: false
}
},
// Streamlined enemy collision processing with categorized collision types
processEnemyCollisions: function processEnemyCollisions() {
var allEnemies = globalEnemyManager.getAllEnemies();
// Category 1: Off-screen cleanup (non-collision processing)
this.processOffScreenCleanup(allEnemies);
// Category 2: Enemy-wizard collisions
this.processEnemyWizardCollisions(allEnemies);
},
// Separate processing for off-screen enemy cleanup
processOffScreenCleanup: function processOffScreenCleanup(allEnemies) {
for (var i = allEnemies.length - 1; i >= 0; i--) {
var enemy = allEnemies[i];
if (this.isOffScreen(enemy)) {
this.removeEnemyFromGame(enemy);
}
}
},
// Separate processing for enemy-wizard collisions
processEnemyWizardCollisions: function processEnemyWizardCollisions(allEnemies) {
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (!enemy.isDying && enemy.parent) {
this.checkWizardCollision(enemy);
}
}
},
// Efficient off-screen detection
isOffScreen: function isOffScreen(enemy) {
return enemy.y > 2732 + 100;
},
// Unified enemy removal system
removeEnemyFromGame: function removeEnemyFromGame(enemy) {
// Remove from global arrays directly
this.removeFromLegacyArrays(enemy);
enemy.destroy();
},
// Legacy array compatibility cleanup
removeFromLegacyArrays: function removeFromLegacyArrays(enemy) {
var arrays = [enemies, ogres, knights, miniBosses];
for (var a = 0; a < arrays.length; a++) {
var array = arrays[a];
for (var i = array.length - 1; i >= 0; i--) {
if (array[i] === enemy) {
array.splice(i, 1);
break;
}
}
}
},
// 1.1 Distance Culling: Enhanced wizard collision detection with optimized distance-based culling
checkWizardCollision: function checkWizardCollision(enemy) {
// Initialize collision tracking
if (enemy.lastIntersecting === undefined) {
enemy.lastIntersecting = false;
}
// 1.1 Distance Culling: Skip expensive intersection test if objects are too far apart
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxCollisionDistance = 150; // Approximate maximum collision distance based on sprite sizes
var currentIntersecting = false;
if (distance <= maxCollisionDistance) {
// Only perform expensive intersection test if objects are close enough
currentIntersecting = wizard.intersects(enemy);
}
// Check collision transition
if (!enemy.lastIntersecting && currentIntersecting && !enemy.isDying) {
var config = this.getEnemyConfig(enemy);
wizard.takeDamage(config.damage);
// Handle enemy removal based on type
if (config.removeOnHit) {
this.removeEnemyFromGame(enemy);
return;
}
}
// Update collision state
enemy.lastIntersecting = currentIntersecting;
},
// Get enemy configuration by type
getEnemyConfig: function getEnemyConfig(enemy) {
return this.collisionConfig[enemy.enemyType] || this.collisionConfig.skeleton;
}
};
// Replace the old collision function call
function checkAllEnemyCollisions() {
CollisionManager.processEnemyCollisions();
}
// Call the unified collision detection function
checkAllEnemyCollisions();
// Check thorns spike collisions with all enemies continuously
var allSpikes = [];
for (var childIdx = 0; childIdx < game.children.length; childIdx++) {
var child = game.children[childIdx];
// Check if this child is a spike (has hitEnemies array and brown tint)
if (child.hitEnemies && child.tint === 0x8B4513) {
allSpikes.push(child);
}
}
for (var spikeIdx = 0; spikeIdx < allSpikes.length; spikeIdx++) {
var spike = allSpikes[spikeIdx];
var allEnemies = collisionArrayPool.getAllEnemies();
for (var enemyIdx = 0; enemyIdx < allEnemies.length; enemyIdx++) {
var enemy = allEnemies[enemyIdx];
// Only hit enemies that haven't been hit by this spike yet and are not dying
if (spike.intersects(enemy) && spike.hitEnemies.indexOf(enemy) === -1 && !enemy.isDying) {
var thornDamage = 100; // Always deal 100 damage
enemy.takeDamage(thornDamage);
LK.effects.flashObject(enemy, 0x8B4513, 300);
// Mark this enemy as hit by this spike
spike.hitEnemies.push(enemy);
}
}
}
// Update spell system
if (gameStarted) {
// Mana regeneration system
manaRegenTimer++;
if (manaRegenTimer >= 60) {
// Every second (60 ticks at 60fps)
manaRegenTimer = 0;
currentMana = Math.min(maxMana, currentMana + manaRegenRate);
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
updateManaDisplay();
}
// Ensure mana bounds are correct
currentMana = Math.max(0, Math.min(maxMana, currentMana));
if (activeSpellDeck) {
activeSpellDeck.currentMana = currentMana;
}
}
// Simple time slow effects processing
var allEnemies = collisionArrayPool.getAllEnemies();
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy.timeSlowed) {
enemy.timeSlowTimer--;
if (enemy.timeSlowTimer <= 0) {
enemy.timeSlowed = false;
enemy.timeSlowAmount = 1.0;
}
}
}
// Make tap text pulse
var pulse = 1 + Math.sin(LK.ticks * 0.1) * 0.2;
tapText.scale.set(pulse, pulse);
// Update targeting cursor animation
if (targetingMode && targetingCursor && targetingCursor.visible) {
// Rotate targeting cursor
targetingCursor.rotation += 0.05;
// Add secondary pulsing animation
var targetPulse = 1 + Math.sin(LK.ticks * 0.2) * 0.3;
if (targetingCursor.tint === 0x00FF00) {
// In-range pulsing
targetingCursor.alpha = 0.6 + targetPulse * 0.2;
} else {
// Out-of-range warning pulse
targetingCursor.alpha = 0.3 + targetPulse * 0.4;
}
// Update orbiting particles around cursor
if (targetingCursor.particles) {
for (var p = 0; p < targetingCursor.particles.length; p++) {
var particle = targetingCursor.particles[p];
if (particle && particle.parent) {
particle.orbitAngle += 0.08;
var orbitRadius = 40 + Math.sin(LK.ticks * 0.1) * 10;
particle.x = targetingCursor.x + Math.cos(particle.orbitAngle) * orbitRadius;
particle.y = targetingCursor.y + Math.sin(particle.orbitAngle) * orbitRadius;
particle.visible = targetingCursor.visible;
particle.tint = targetingCursor.tint;
particle.alpha = targetingCursor.alpha * 0.7;
}
}
}
}
// Update targeting range indicator
if (targetingMode && targetingRange && targetingRange.visible) {
// Gentle pulsing for range indicator
var rangePulse = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
targetingRange.scaleX = 8.0 * rangePulse;
targetingRange.scaleY = 8.0 * rangePulse;
targetingRange.alpha = 0.2 + Math.sin(LK.ticks * 0.15) * 0.1;
}
// Check for spell unlocks
checkSpellUnlocks();
// Clean up any orphaned projectiles that may not have been properly removed
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
if (!projectile || !projectile.parent || projectile.hitEnemy) {
projectiles.splice(i, 1);
}
}
// ProjectileFactory uses the global projectiles array, no separate activeProjectiles array needed
};
// Remove tap text after 5 seconds
tween({}, {}, {
duration: 5000,
onFinish: function onFinish() {
if (tapText && tapText.parent) {
tapText.destroy();
}
}
}); ===================================================================
--- original.js
+++ change.js
@@ -1705,23 +1705,48 @@
console.log('Mana bar updated successfully');
// SPELL SLOT SYSTEM REMOVED - Using deck menu only for spell casting
// Spells can now only be cast from the deck menu interface
console.log('Spell slots eliminated - using deck menu system only');
- // ADDITIONAL MANA VERIFICATION - ENSURE MANA STAYS AT 100
+ // CRITICAL MANA SYSTEM INITIALIZATION AND REPAIR
+ console.log('=== CRITICAL MANA INITIALIZATION ===');
+ // STEP 1: Force mana to 100 immediately
+ currentMana = 100;
+ maxMana = 100;
+ // STEP 2: Force activeSpellDeck mana to 100
+ if (activeSpellDeck) {
+ activeSpellDeck.currentMana = 100;
+ activeSpellDeck.maxMana = 100;
+ }
+ // STEP 3: Clear any corrupted cooldowns
+ cardCooldowns = {};
+ // STEP 4: Force mana validation and repair
+ validateManaSystem();
+ // STEP 5: Update mana display immediately
+ updateManaDisplay();
+ // STEP 6: Additional verification with delay to ensure all systems sync
tween({}, {}, {
duration: 100,
- // Short delay to ensure all systems are initialized
onFinish: function onFinish() {
// FORCE MANA TO 100 AGAIN after all initialization
currentMana = 100;
- activeSpellDeck.currentMana = 100;
- // Mana system handled by deck menu
- console.log('Mana system initialized through deck menu');
+ if (activeSpellDeck) {
+ activeSpellDeck.currentMana = 100;
+ }
+ // Force update display again
+ updateManaDisplay();
// Final debug verification
console.log('=== FINAL MANA VERIFICATION ===');
console.log('Final currentMana:', currentMana);
- console.log('Final activeSpellDeck.currentMana:', activeSpellDeck.currentMana);
- console.log('Mana system successfully initialized to 100');
+ console.log('Final activeSpellDeck.currentMana:', activeSpellDeck ? activeSpellDeck.currentMana : 'undefined');
+ console.log('Final maxMana:', maxMana);
+ console.log('Mana system successfully forced to 100');
+ // Additional safety check - validate one more time
+ if (currentMana !== 100) {
+ console.log('⚠️ EMERGENCY MANA FIX');
+ currentMana = 100;
+ if (activeSpellDeck) activeSpellDeck.currentMana = 100;
+ updateManaDisplay();
+ }
}
});
// SPELL SLOTS SYSTEM REMOVED
// All spell casting now happens through the deck menu system only