User prompt
Procede con el paso 2E
User prompt
procede con el paso 2D
User prompt
procede con 2C
User prompt
procede con el paso 2B
User prompt
empieza con el paso 2A
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'skeleton')' in or related to this line: 'var base = this.Enemy[type];' Line Number: 3780
User prompt
confirmo que procedas con el paso 1E
User prompt
haz el 1D
User prompt
si haz el paso 1C
User prompt
haz el paso 1B
User prompt
perfecto haz el paso 1A
User prompt
elimina las upgrades
User prompt
elimina la tienda mejora
User prompt
implementa todas las mejoras del juego en el deck de hechizos
User prompt
el deck de hechizos no se entiende
User prompt
crea un deck de hechizos en el menu principal
User prompt
simplifica el sistema de mejoras
User prompt
haz mas eficiente los sistemas de spawn
User prompt
Please fix the bug: 'ReferenceError: enemyType is not defined' in or related to this line: 'if (enemyType === 'miniBoss') {' Line Number: 146
User prompt
Please fix the bug: 'ReferenceError: enemyType is not defined' in or related to this line: 'if (enemyType === 'miniBoss') {' Line Number: 146
User prompt
crea un sistema unificado de configuracion de enemigos y usa patrones de herencia para cada clase
User prompt
bueno haz el tutorial mas basico y confiable
User prompt
mejora el tutorial con ejemplos interactivos
User prompt
implementa tap directo al enemigo
User prompt
quita los combos
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BaseEnemy = Container.expand(function (enemyType, config) {
var self = Container.call(this);
// Initialize common properties
self.enemyType = enemyType || '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;
// Get unified configuration
var enemyConfig = GAME_CONFIG.enemies[self.enemyType];
if (!enemyConfig) {
console.error('Unknown enemy type:', self.enemyType);
enemyConfig = GAME_CONFIG.enemies.skeleton;
}
// Apply configuration with overrides
self.typeConfig = GAME_CONFIG.createEnemyConfig(self.enemyType, config || {});
self.animationSpeed = self.typeConfig.animationSpeed || 15;
self.health = self.typeConfig.health || self.typeConfig.baseHealth;
self.maxHealth = self.health;
self.speed = self.typeConfig.speed || self.typeConfig.baseSpeed;
// Create animation system
self.createAnimationFrames = function () {
self.animationFrames = [];
for (var i = 1; i <= 4; i++) {
var frameGraphics = self.attachAsset(self.typeConfig.assetPrefix + i, {
anchorX: 0.5,
anchorY: 1.0,
scaleX: self.typeConfig.scale,
scaleY: self.typeConfig.scale
});
frameGraphics.visible = i === 1;
if (self.typeConfig.tint) frameGraphics.tint = self.typeConfig.tint;
self.animationFrames.push(frameGraphics);
}
};
// Create hitbox
self.createHitbox = function () {
var hitbox = self.attachAsset(self.typeConfig.assetPrefix + '1', {
anchorX: 0.5,
anchorY: 1.0,
scaleX: self.typeConfig.scale * 2,
scaleY: self.typeConfig.scale * 2
});
hitbox.alpha = 0;
return hitbox;
};
// Initialize visual components
self.createAnimationFrames();
self.createHitbox();
return self;
});
// Game arrays to track objects
var Enemy = BaseEnemy.expand(function (type, config) {
var self = BaseEnemy.call(this, type, config);
// Define createHealthBar method before it's used
self.createHealthBar = function () {
if (self.enemyType !== 'miniBoss') return;
self.healthBarBg = game.addChild(LK.getAsset('miniBossHealthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_CONFIG.ui.miniBossHealthBar.x,
y: GAME_CONFIG.ui.miniBossHealthBar.y,
scaleX: GAME_CONFIG.ui.miniBossHealthBar.bgScaleX,
scaleY: GAME_CONFIG.ui.miniBossHealthBar.bgScaleY
}));
self.healthBarFg = game.addChild(LK.getAsset('miniBossHealthBar', {
anchorX: 0.0,
anchorY: 0.5,
x: GAME_CONFIG.ui.miniBossHealthBar.x - 200,
y: GAME_CONFIG.ui.miniBossHealthBar.y,
scaleX: GAME_CONFIG.ui.miniBossHealthBar.fgScaleX,
scaleY: GAME_CONFIG.ui.miniBossHealthBar.fgScaleY
}));
self.healthText = new Text2('Boss Health: ' + self.health + '/' + self.maxHealth, {
size: GAME_CONFIG.ui.miniBossHealthBar.textSize,
fill: 0xFFFFFF,
font: "monospace"
});
self.healthText.anchor.set(0.5, 0.5);
self.healthText.x = GAME_CONFIG.ui.miniBossHealthBar.x;
self.healthText.y = GAME_CONFIG.ui.miniBossHealthBar.textY;
game.addChild(self.healthText);
};
// Special properties for mini boss
if (self.enemyType === 'miniBoss') {
self.createHealthBar();
}
self.updateHealthBar = function () {
if (self.enemyType !== 'miniBoss' || !self.healthBarFg) return;
var healthPercent = self.health / self.maxHealth;
self.healthBarFg.scaleX = healthPercent;
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;
}
};
self.update = function () {
if (tutorial && tutorial.isActive) {
return;
}
if (self.isDying) {
return;
}
var speedMultiplier = 1.0;
// Upgrade menu removed - no speed modification needed
// Animation system
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;
}
// Speed progression
if (!self.speedTweenStarted) {
self.speedTweenStarted = true;
var targetSpeed = self.speed * 1.5;
tween(self, {
speed: targetSpeed
}, {
duration: 10000,
easing: tween.easeOut
});
}
// Handle frozen state
if (self.frozen) {
self.frozenTimer--;
if (self.frozenTimer <= 0) {
self.frozen = false;
}
return;
}
// 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 finalSpeedMultiplier = speedMultiplier;
// Apply time slow effect
if (self.timeSlowed) {
finalSpeedMultiplier *= self.timeSlowAmount;
}
self.x += dx / distance * self.speed * finalSpeedMultiplier;
self.y += dy / distance * self.speed * finalSpeedMultiplier;
}
// Face direction
var flipScale = dx < 0 ? -self.typeConfig.scale : self.typeConfig.scale;
for (var frameIdx = 0; frameIdx < self.animationFrames.length; frameIdx++) {
self.animationFrames[frameIdx].scaleX = flipScale;
}
} else {
self.y += self.speed * speedMultiplier;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.animationState = 'attacking';
LK.effects.flashObject(self, 0xFF0000, 50);
// Update health bar for mini boss
if (self.enemyType === 'miniBoss') {
self.updateHealthBar();
}
// Create damage text
var damageText = new Text2('-' + damage, {
size: self.typeConfig.damageTextSize,
fill: self.typeConfig.damageTextColor,
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);
tween(damageText, {
y: damageText.y - 120,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
damageText.destroy();
}
});
// Create impact effect using unified function
createImpactEffect(self);
// Return to walking after attack animation
tween({}, {}, {
duration: 300,
onFinish: function onFinish() {
if (self.animationState === 'attacking') {
self.animationState = 'walking';
}
}
});
if (self.health <= 0) {
self.die();
}
};
self.down = function (x, y, obj) {
// Don't allow attacking during tutorial
if (tutorial && tutorial.isActive) {
return;
}
// Upgrade menu removed - no attack blocking needed
// Don't allow attacking dying enemies
if (self.isDying) {
return;
}
// Check wizard's attack cooldown
if (wizard && wizard.attackCooldown > 0) {
// Flash red to indicate cooldown
LK.effects.flashObject(self, 0xFF0000, 200);
return;
}
// Vibration feedback
if (typeof LK.vibrate === 'function') {
if (Array.isArray(self.typeConfig.vibration)) {
LK.vibrate(self.typeConfig.vibration);
} else {
LK.vibrate(self.typeConfig.vibration);
}
}
// Visual feedback for successful tap
selectedEnemy = self;
LK.effects.flashObject(self, 0xFFFF00, 500);
// Create projectile from wizard to this enemy
if (wizard && projectiles.length < 10) {
var projectile = game.addChild(new UnifiedProjectile('projectile'));
projectile.x = wizard.x;
projectile.y = wizard.y;
var dx = self.x - wizard.x;
var dy = self.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
}
projectile.targetEnemy = self;
projectiles.push(projectile);
LK.getSound('spellCast').play();
// Set wizard's attack cooldown
wizard.attackCooldown = 30; // 0.5 seconds at 60fps
}
};
self.die = function () {
// Determine appropriate enemy array based on type
var arrayToUpdate = null;
if (self.enemyType === 'skeleton') arrayToUpdate = enemies;else if (self.enemyType === 'ogre') arrayToUpdate = ogres;else if (self.enemyType === 'knight') arrayToUpdate = knights;else if (self.enemyType === 'miniBoss') arrayToUpdate = miniBosses;
// Call unified death animation function
createEnemyDeathAnimation(self, self.enemyType, arrayToUpdate);
};
return self;
});
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 knight
if (knight && self.intersects(knight)) {
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;
}
}
self.destroy();
};
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;
}
// Upgrade menu removed - no pause needed
// 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 = enemies.concat(ogres).concat(knights).concat(miniBosses);
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
var energyBeam = game.addChild(new UnifiedProjectile('energyBeam'));
energyBeam.x = self.x;
energyBeam.y = self.y;
energyBeam.targetEnemy = closestEnemy;
// Calculate direction to target
var dx = closestEnemy.x - self.x;
var dy = closestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
energyBeam.direction.x = dx / distance;
energyBeam.direction.y = dy / distance;
}
// Flash effect on sphere when attacking
tween(sphereGraphics, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(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);
// 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);
}
// Button interaction
self.down = function (x, y, obj) {
// Handle configuration menu interactions
if (self.configMode) {
// Music volume adjustment
if (y >= 1150 && y <= 1250) {
var currentMusicVolume = storage.musicVolume || 0.7;
var newMusicVolume = Math.min(1.0, currentMusicVolume + 0.1);
if (newMusicVolume > 1.0) newMusicVolume = 0.0;
storage.musicVolume = newMusicVolume;
self.musicVolumeText.setText('VOLUMEN MUSICA: ' + Math.round(newMusicVolume * 100) + '%');
return;
}
// Sound volume adjustment
if (y >= 1350 && y <= 1450) {
var currentSoundVolume = storage.soundVolume || 1.0;
var newSoundVolume = Math.min(1.0, currentSoundVolume + 0.1);
if (newSoundVolume > 1.0) newSoundVolume = 0.0;
storage.soundVolume = newSoundVolume;
self.soundVolumeText.setText('VOLUMEN SONIDO: ' + Math.round(newSoundVolume * 100) + '%');
return;
}
// Difficulty adjustment
if (y >= 1550 && y <= 1650) {
var currentDifficulty = storage.difficulty || 'NORMAL';
var difficulties = ['FACIL', 'NORMAL', 'DIFICIL'];
var currentIndex = difficulties.indexOf(currentDifficulty);
var newIndex = (currentIndex + 1) % difficulties.length;
var newDifficulty = difficulties[newIndex];
storage.difficulty = newDifficulty;
self.difficultyText.setText('DIFICULTAD: ' + newDifficulty);
return;
}
// Back button
if (y >= 1950 && y <= 2050) {
self.hideConfigMenu();
return;
}
// Block all other interactions when config menu is active
return;
}
// Handle deck menu interactions
if (self.deckMode) {
// Check deck card clicks with better hit detection
for (var i = 0; i < self.deckElements.length; i++) {
var element = self.deckElements[i];
if (element.spellId && element.isDeckCard) {
var cardX = element.x;
var cardY = element.y;
if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) {
// Visual feedback before removal
LK.effects.flashObject(element, 0xFF0000, 300);
// Remove from deck
if (self.spellDeck.removeFromDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0xFF8800, 200);
// Show removal message
var removeText = new Text2('HECHIZO REMOVIDO', {
size: 60,
fill: 0xFF6666,
font: "monospace"
});
removeText.anchor.set(0.5, 0.5);
removeText.x = 2048 / 2;
removeText.y = 2200;
self.addChild(removeText);
// Animate and remove message
tween(removeText, {
alpha: 0,
y: removeText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (removeText.parent) removeText.destroy();
}
});
} else {
LK.effects.flashScreen(0xFF0000, 200);
}
return;
}
}
}
// Check available card clicks with better hit detection
for (var i = 0; i < self.availableElements.length; i++) {
var element = self.availableElements[i];
if (element.spellId && !element.isDeckCard) {
var cardX = element.x;
var cardY = element.y;
if (x >= cardX - 175 && x <= cardX + 175 && y >= cardY - 225 && y <= cardY + 225) {
// Visual feedback before addition
LK.effects.flashObject(element, 0x00FF00, 300);
// Add to deck
if (self.spellDeck.addToDeck(element.spellId)) {
self.refreshDeckDisplay();
LK.effects.flashScreen(0x00FF00, 200);
// Show addition message
var addText = new Text2('HECHIZO AÑADIDO', {
size: 60,
fill: 0x66FF66,
font: "monospace"
});
addText.anchor.set(0.5, 0.5);
addText.x = 2048 / 2;
addText.y = 2200;
self.addChild(addText);
// Animate and remove message
tween(addText, {
alpha: 0,
y: addText.y - 100
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (addText.parent) addText.destroy();
}
});
} else {
LK.effects.flashScreen(0xFF0000, 200);
// Show error message
var errorText = new Text2('DECK LLENO (MAX 5)', {
size: 50,
fill: 0xFF6666,
font: "monospace"
});
errorText.anchor.set(0.5, 0.5);
errorText.x = 2048 / 2;
errorText.y = 2200;
self.addChild(errorText);
// Animate and remove message
tween(errorText, {
alpha: 0,
y: errorText.y - 100
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (errorText.parent) errorText.destroy();
}
});
}
return;
}
}
}
// Deck back button
if (y >= 2350 && y <= 2650) {
self.hideDeck();
}
// Block all other interactions when deck menu is active
return;
}
// Handle shop menu interactions
if (self.shopMode) {
// Shop item purchase buttons
for (var i = 0; i < 3; i++) {
var itemY = 1100 + i * 200;
if (y >= itemY - 50 && y <= itemY + 50 && x >= 2048 / 2 + 200 && x <= 2048 / 2 + 400) {
// Purchase item logic
var shopItems = [{
name: 'POCION SALUD',
cost: 10
}, {
name: 'ESCUDO MAGICO',
cost: 15
}, {
name: 'ESPADA MALDITA',
cost: 20
}];
var item = shopItems[i];
if (coinCounter >= item.cost) {
coinCounter -= item.cost;
// Apply item effect based on type
if (i === 0) {
// Health potion
if (wizard) {
wizard.health = Math.min(wizard.health + 50, wizard.maxHealth);
updateHealthBar();
}
} else if (i === 1) {
// Magic shield
if (wizard) {
wizard.shieldActive = true;
wizard.maxShieldHits = 3;
wizard.currentShieldHits = 0;
}
} else if (i === 2) {
// Cursed sword - temporary damage boost
if (wizard) {
wizard.tempDamageBoost = true;
wizard.tempDamageTimer = 1800; // 30 seconds at 60fps
}
}
LK.effects.flashScreen(0x00FF00, 300);
} else {
LK.effects.flashScreen(0xFF0000, 300);
}
return;
}
}
// Shop back button
if (y >= 1950 && y <= 2050) {
self.hideShop();
return;
}
// Block all other interactions when shop menu is active
return;
}
// Check if configuration button was clicked
if (y >= 1700 && y <= 1900 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show configuration menu
self.showConfigMenu();
} else if (y >= 1850 && y <= 2050 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show shop menu
self.showShop();
} else if (y >= 2050 && y <= 2250 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show deck menu
self.showDeck();
} else if (storage.tutorialCompleted && y >= 2250 && y <= 2450 && x >= 2048 / 2 - 200 && x <= 2048 / 2 + 200) {
// Show tutorial again
if (tutorial) {
self.visible = false;
tutorial.startTutorial();
}
} else if (y >= 1450 && y <= 1650) {
// Start the game by hiding menu
self.startGame();
}
};
self.showConfigMenu = function () {
// Create configuration overlay
if (!self.configOverlay) {
self.configOverlay = self.addChild(LK.getAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1.0,
scaleY: 1.0
}));
self.configOverlay.alpha = 1.0;
self.configOverlay.tint = 0x000000;
// Config title
var configTitle = new Text2('CONFIGURACION', {
size: 120,
fill: 0xFFD700,
font: "monospace"
});
configTitle.anchor.set(0.5, 0.5);
configTitle.x = 2048 / 2;
configTitle.y = 800;
self.addChild(configTitle);
self.configTitle = configTitle;
// Music volume setting
var musicVolumeText = new Text2('VOLUMEN MUSICA: ' + Math.round((storage.musicVolume || 0.7) * 100) + '%', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
musicVolumeText.anchor.set(0.5, 0.5);
musicVolumeText.x = 2048 / 2;
musicVolumeText.y = 1200;
self.addChild(musicVolumeText);
self.musicVolumeText = musicVolumeText;
// Sound volume setting
var soundVolumeText = new Text2('VOLUMEN SONIDO: ' + Math.round((storage.soundVolume || 1.0) * 100) + '%', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
soundVolumeText.anchor.set(0.5, 0.5);
soundVolumeText.x = 2048 / 2;
soundVolumeText.y = 1400;
self.addChild(soundVolumeText);
self.soundVolumeText = soundVolumeText;
// Difficulty setting
var difficultyText = new Text2('DIFICULTAD: ' + (storage.difficulty || 'NORMAL'), {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
difficultyText.anchor.set(0.5, 0.5);
difficultyText.x = 2048 / 2;
difficultyText.y = 1600;
self.addChild(difficultyText);
self.difficultyText = difficultyText;
// Back button
var backButton = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2000,
scaleX: 2,
scaleY: 2
});
backButton.tint = 0x00FF00;
self.backButton = backButton;
var backText = new Text2('VOLVER', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
backText.anchor.set(0.5, 0.5);
backText.x = 2048 / 2;
backText.y = 2000;
self.addChild(backText);
self.backText = backText;
}
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 () {
// Create shop overlay
if (!self.shopOverlay) {
self.shopOverlay = self.addChild(LK.getAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1.0,
scaleY: 1.0
}));
self.shopOverlay.alpha = 1.0;
self.shopOverlay.tint = 0x000033;
// Shop title
var shopTitle = new Text2('TIENDA', {
size: 120,
fill: 0xFFD700,
font: "monospace"
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.x = 2048 / 2;
shopTitle.y = 800;
self.addChild(shopTitle);
self.shopTitle = shopTitle;
// Shop items
var shopItems = [{
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'
}];
// Initialize shop elements arrays if not exists
if (!self.shopIcons) self.shopIcons = [];
if (!self.shopTexts) self.shopTexts = [];
if (!self.shopBuyButtons) self.shopBuyButtons = [];
if (!self.shopBuyTexts) self.shopBuyTexts = [];
// Display shop items
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var yPos = 1100 + i * 200;
// Item icon
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);
// Item text
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);
// Buy button
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);
}
// Back button
var shopBackButton = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2000,
scaleX: 2,
scaleY: 2
});
shopBackButton.tint = 0x00FF00;
self.shopBackButton = shopBackButton;
var shopBackText = new Text2('VOLVER', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
shopBackText.anchor.set(0.5, 0.5);
shopBackText.x = 2048 / 2;
shopBackText.y = 2000;
self.addChild(shopBackText);
self.shopBackText = shopBackText;
}
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 () {
// Create deck overlay
if (!self.deckOverlay) {
self.deckOverlay = self.addChild(LK.getAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1.0,
scaleY: 1.0
}));
self.deckOverlay.alpha = 1.0;
self.deckOverlay.tint = 0x1a0a2e;
// Deck title
var deckTitle = new Text2('DECK DE HECHIZOS', {
size: 100,
fill: 0xFFD700,
font: "monospace"
});
deckTitle.anchor.set(0.5, 0.5);
deckTitle.x = 2048 / 2;
deckTitle.y = 600;
self.addChild(deckTitle);
self.deckTitle = deckTitle;
// Initialize deck arrays
if (!self.deckElements) self.deckElements = [];
if (!self.availableElements) self.availableElements = [];
// Create spell deck instance
if (!self.spellDeck) self.spellDeck = new SpellDeck();
self.refreshDeckDisplay();
// Deck back button
var deckBackButton = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2500,
scaleX: 2,
scaleY: 2
});
deckBackButton.tint = 0x00FF00;
self.deckBackButton = deckBackButton;
var deckBackText = new Text2('VOLVER', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
deckBackText.anchor.set(0.5, 0.5);
deckBackText.x = 2048 / 2;
deckBackText.y = 2500;
self.addChild(deckBackText);
self.deckBackText = deckBackText;
}
self.deckOverlay.visible = true;
self.deckMode = true;
self.refreshDeckDisplay();
};
self.refreshDeckDisplay = function () {
if (!self.spellDeck) return;
// 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 glow effect
var cardBg = self.attachAsset('spellCard', {
anchorX: 0.5,
anchorY: 0.5,
x: cardX,
y: cardY,
scaleX: 3.5,
scaleY: 4.5
});
cardBg.tint = self.spellDeck.getRarityColor(spell.rarity);
cardBg.spellId = spell.id;
cardBg.isDeckCard = true;
cardBg.alpha = 0.9;
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 cardDesc = new Text2(spell.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);
// 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: 20,
fill: 0xFFD700,
font: "monospace"
});
cardStats.anchor.set(0.5, 0.5);
cardStats.x = cardX;
cardStats.y = cardY + 80;
self.addChild(cardStats);
self.deckElements.push(cardStats);
}
}
} 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 spell cards
var availableSpells = [];
for (var i = 0; i < self.spellDeck.availableSpells.length; i++) {
var spell = self.spellDeck.availableSpells[i];
if (spell.unlocked && 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;
};
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;
}
// 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 spell UI
if (manaBarBg) manaBarBg.visible = true;
if (manaBar) manaBar.visible = true;
if (manaText) manaText.visible = true;
for (var i = 0; i < spellSlots.length; i++) {
spellSlots[i].visible = true;
}
// 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;
});
// All enemy types now use the unified Enemy class with type parameter
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
self.update = function () {
// Pause orb when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Upgrade menu removed - no pause needed
// 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;
// Check collision with enemies using collision state tracking (but never with wizard)
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
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;
}
var 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
tween(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;
}
};
return self;
});
var SpellDeck = Container.expand(function () {
var self = Container.call(this);
// Available spells in the game with enhanced mechanics
self.availableSpells = [{
id: 'fireball',
name: 'FIREBALL',
description: 'Launches a burning projectile',
damage: 150,
cooldown: 3000,
manaCost: 30,
unlocked: true,
rarity: 'common',
element: 'fire',
chainTargets: 0,
areaRadius: 0
}, {
id: 'iceShard',
name: 'ICE SHARD',
description: 'Freezes and damages enemies',
damage: 100,
cooldown: 2000,
manaCost: 20,
unlocked: true,
rarity: 'common',
element: 'ice',
freezeDuration: 2000,
slowAmount: 0.5
}, {
id: 'lightning',
name: 'LIGHTNING',
description: 'Chain lightning between enemies',
damage: 200,
cooldown: 4000,
manaCost: 40,
unlocked: false,
rarity: 'rare',
element: 'lightning',
chainTargets: 3,
chainRange: 200
}, {
id: 'heal',
name: 'HEAL',
description: 'Restores wizard health',
healing: 50,
cooldown: 5000,
manaCost: 25,
unlocked: true,
rarity: 'common',
element: 'light',
healOverTime: 10,
healDuration: 3000
}, {
id: 'shield',
name: 'MAGIC SHIELD',
description: 'Temporary damage immunity',
duration: 3000,
cooldown: 8000,
manaCost: 50,
unlocked: false,
rarity: 'epic',
element: 'arcane',
shieldStrength: 3,
reflectDamage: true
}, {
id: 'meteor',
name: 'METEOR',
description: 'Massive area damage',
damage: 500,
cooldown: 10000,
manaCost: 80,
unlocked: false,
rarity: 'legendary',
element: 'fire',
areaRadius: 300,
stunDuration: 1000
}, {
id: 'teleport',
name: 'TELEPORT',
description: 'Instantly move wizard',
cooldown: 6000,
manaCost: 35,
unlocked: false,
rarity: 'rare',
element: 'arcane',
teleportRange: 400,
invulnDuration: 500
}, {
id: 'timeSlow',
name: 'TIME SLOW',
description: 'Slows all enemies',
duration: 4000,
cooldown: 12000,
manaCost: 60,
unlocked: false,
rarity: 'epic',
element: 'time',
slowAmount: 0.3,
damageBonus: 1.5
}];
// Current deck (max 5 spells)
self.currentDeck = storage.spellDeck || ['fireball', 'iceShard', 'heal'];
// Active spell cooldowns
self.spellCooldowns = {};
// Mana system
self.maxMana = 100;
self.currentMana = self.maxMana;
self.manaRegenRate = 2; // Mana per second
self.manaRegenTimer = 0;
// Combo system
self.lastSpellCast = 0;
self.comboMultiplier = 1.0;
self.comboTimer = 0;
// Spell evolution system
self.spellEvolutions = {};
// Enhanced spell mechanics
self.canCastSpell = function (spellId) {
var spell = self.getSpell(spellId);
if (!spell) return false;
// Check mana
if (self.currentMana < spell.manaCost) return false;
// Check cooldown
if (self.spellCooldowns[spellId] && self.spellCooldowns[spellId] > LK.ticks) return false;
return true;
};
// Cast spell with enhanced mechanics
self.castSpell = function (spellId, targetX, targetY) {
if (!self.canCastSpell(spellId)) return false;
var spell = self.getSpell(spellId);
if (!spell) return false;
// Consume mana
self.currentMana -= spell.manaCost;
// Set cooldown
self.spellCooldowns[spellId] = LK.ticks + spell.cooldown / 1000 * 60; // Convert to ticks
// Update combo system
self.updateCombo();
// Apply spell effects
self.applySpellEffect(spell, targetX, targetY);
// Update spell evolution
self.updateSpellEvolution(spellId);
return true;
};
// Update combo system
self.updateCombo = function () {
var currentTime = LK.ticks;
if (currentTime - self.lastSpellCast < 180) {
// Within 3 seconds
self.comboMultiplier = Math.min(self.comboMultiplier + 0.2, 3.0);
self.comboTimer = 180; // Reset combo timer
} else {
self.comboMultiplier = 1.0;
}
self.lastSpellCast = currentTime;
};
// Apply spell effects
self.applySpellEffect = function (spell, targetX, targetY) {
switch (spell.id) {
case 'fireball':
self.castFireball(spell, targetX, targetY);
break;
case 'iceShard':
self.castIceShard(spell, targetX, targetY);
break;
case 'lightning':
self.castLightning(spell, targetX, targetY);
break;
case 'heal':
self.castHeal(spell);
break;
case 'shield':
self.castShield(spell);
break;
case 'meteor':
self.castMeteor(spell, targetX, targetY);
break;
case 'teleport':
self.castTeleport(spell, targetX, targetY);
break;
case 'timeSlow':
self.castTimeSlow(spell);
break;
}
};
// Individual spell implementations
self.castFireball = function (spell, targetX, targetY) {
var projectile = game.addChild(new UnifiedProjectile('fireBall'));
projectile.x = wizard.x;
projectile.y = wizard.y;
projectile.damage = spell.damage * self.comboMultiplier;
var dx = targetX - wizard.x;
var dy = targetY - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
}
projectiles.push(projectile);
LK.getSound('fireWhoosh').play();
};
self.castIceShard = function (spell, targetX, targetY) {
var projectile = game.addChild(new UnifiedProjectile('projectile'));
projectile.x = wizard.x;
projectile.y = wizard.y;
projectile.damage = spell.damage * self.comboMultiplier;
projectile.freezeEffect = true;
projectile.freezeDuration = spell.freezeDuration;
var dx = targetX - wizard.x;
var dy = targetY - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
}
projectiles.push(projectile);
LK.getSound('iceFreeze').play();
};
self.castLightning = function (spell, targetX, targetY) {
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
var hitEnemies = [];
var damage = spell.damage * self.comboMultiplier;
// Find closest enemy to target point
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
var dx = enemy.x - targetX;
var dy = enemy.y - targetY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
// Chain lightning
var currentTarget = closestEnemy;
for (var chain = 0; chain < spell.chainTargets && currentTarget; chain++) {
currentTarget.takeDamage(damage);
hitEnemies.push(currentTarget);
LK.effects.flashObject(currentTarget, 0x00FFFF, 300);
// Create lightning effect
var lightning = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: currentTarget.x,
y: currentTarget.y,
scaleX: 2,
scaleY: 2
}));
lightning.tint = 0x00FFFF;
tween(lightning, {
alpha: 0,
scaleX: 4,
scaleY: 4
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
lightning.destroy();
}
});
// Find next target
var nextTarget = null;
var nextDistance = Infinity;
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (hitEnemies.indexOf(enemy) === -1) {
var dx = enemy.x - currentTarget.x;
var dy = enemy.y - currentTarget.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < spell.chainRange && distance < nextDistance) {
nextDistance = distance;
nextTarget = enemy;
}
}
}
currentTarget = nextTarget;
}
};
self.castHeal = function (spell) {
var healing = spell.healing * self.comboMultiplier;
wizard.health = Math.min(wizard.health + healing, wizard.maxHealth);
updateHealthBar();
LK.effects.flashObject(wizard, 0x00FF00, 500);
};
self.castShield = function (spell) {
wizard.shieldActive = true;
wizard.maxShieldHits = spell.shieldStrength;
wizard.currentShieldHits = 0;
wizard.shieldReflect = spell.reflectDamage;
LK.effects.flashObject(wizard, 0x0080FF, 500);
};
self.castMeteor = function (spell, targetX, targetY) {
var meteor = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: targetX,
y: targetY - 500,
scaleX: 3,
scaleY: 3
}));
meteor.tint = 0xFF4500;
tween(meteor, {
y: targetY,
scaleX: 5,
scaleY: 5
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
// Area damage
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
var dx = enemy.x - targetX;
var dy = enemy.y - targetY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= spell.areaRadius) {
enemy.takeDamage(spell.damage * self.comboMultiplier);
enemy.frozen = true;
enemy.frozenTimer = spell.stunDuration / 1000 * 60;
}
}
LK.effects.flashScreen(0xFF4500, 500);
meteor.destroy();
}
});
};
self.castTeleport = function (spell, targetX, targetY) {
var oldX = wizard.x;
var oldY = wizard.y;
wizard.x = targetX;
wizard.y = targetY;
// Teleport effects
LK.effects.flashObject(wizard, 0x8000FF, 300);
// Invulnerability
wizard.teleportInvuln = true;
tween({}, {}, {
duration: spell.invulnDuration,
onFinish: function onFinish() {
wizard.teleportInvuln = false;
}
});
};
self.castTimeSlow = function (spell) {
// Apply time slow to all enemies
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
enemy.timeSlowed = true;
enemy.timeSlowAmount = spell.slowAmount;
enemy.timeSlowTimer = spell.duration / 1000 * 60;
}
LK.effects.flashScreen(0x8000FF, 300);
};
// Update mana regeneration
self.updateMana = function () {
self.manaRegenTimer++;
if (self.manaRegenTimer >= 60) {
// Every second
self.manaRegenTimer = 0;
self.currentMana = Math.min(self.currentMana + self.manaRegenRate, self.maxMana);
}
};
// Update combo timer
self.updateCombo = function () {
if (self.comboTimer > 0) {
self.comboTimer--;
if (self.comboTimer <= 0) {
self.comboMultiplier = 1.0;
}
}
};
// Update spell evolution
self.updateSpellEvolution = function (spellId) {
if (!self.spellEvolutions[spellId]) {
self.spellEvolutions[spellId] = 0;
}
self.spellEvolutions[spellId]++;
// Check for evolution milestones
if (self.spellEvolutions[spellId] % 10 === 0) {
self.evolveSpell(spellId);
}
};
// Evolve spell
self.evolveSpell = function (spellId) {
var spell = self.getSpell(spellId);
if (!spell) return;
// Enhance spell based on usage
switch (spellId) {
case 'fireball':
spell.damage += 25;
spell.areaRadius += 20;
break;
case 'iceShard':
spell.damage += 15;
spell.freezeDuration += 500;
break;
case 'lightning':
spell.chainTargets += 1;
spell.chainRange += 50;
break;
case 'heal':
spell.healing += 10;
break;
}
};
// Get spell evolution level
self.getEvolutionLevel = function (spellId) {
return Math.floor((self.spellEvolutions[spellId] || 0) / 10);
};
// Unlock spell based on achievements
self.unlockSpell = function (spellId) {
for (var i = 0; i < self.availableSpells.length; i++) {
if (self.availableSpells[i].id === spellId) {
self.availableSpells[i].unlocked = true;
storage.unlockedSpells = storage.unlockedSpells || [];
if (storage.unlockedSpells.indexOf(spellId) === -1) {
storage.unlockedSpells.push(spellId);
}
break;
}
}
};
// Load unlocked spells from storage
self.loadUnlockedSpells = function () {
var unlockedSpells = storage.unlockedSpells || [];
for (var i = 0; i < unlockedSpells.length; i++) {
var spellId = unlockedSpells[i];
for (var j = 0; j < self.availableSpells.length; j++) {
if (self.availableSpells[j].id === spellId) {
self.availableSpells[j].unlocked = true;
break;
}
}
}
};
// Add spell to deck
self.addToDeck = function (spellId) {
if (self.currentDeck.length >= 5) return false;
if (self.currentDeck.indexOf(spellId) !== -1) return false;
var spell = self.getSpell(spellId);
if (spell && spell.unlocked) {
self.currentDeck.push(spellId);
storage.spellDeck = self.currentDeck;
return true;
}
return false;
};
// Remove spell from deck
self.removeFromDeck = function (spellId) {
var index = self.currentDeck.indexOf(spellId);
if (index !== -1) {
self.currentDeck.splice(index, 1);
storage.spellDeck = self.currentDeck;
return true;
}
return false;
};
// Get spell data by ID
self.getSpell = function (spellId) {
for (var i = 0; i < self.availableSpells.length; i++) {
if (self.availableSpells[i].id === spellId) {
return self.availableSpells[i];
}
}
return null;
};
// Get rarity color
self.getRarityColor = function (rarity) {
switch (rarity) {
case 'common':
return 0xFFFFFF;
case 'rare':
return 0x0080FF;
case 'epic':
return 0x8000FF;
case 'legendary':
return 0xFF8000;
default:
return 0xFFFFFF;
}
};
// Initialize unlocked spells
self.loadUnlockedSpells();
return self;
});
var Tutorial = Container.expand(function () {
var self = Container.call(this);
// Tutorial state variables
self.currentStep = 0;
self.isActive = false;
self.skipped = false; // Track if tutorial was skipped
self.tutorialSteps = [];
self.highlightElements = [];
self.tutorialTexts = [];
self.arrows = [];
// Tutorial overlay background
var tutorialOverlay = self.attachAsset('startMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1.0,
scaleY: 1.0
});
tutorialOverlay.alpha = 0.8;
tutorialOverlay.tint = 0x000000;
tutorialOverlay.visible = false; // Initially hidden
tutorialOverlay.zIndex = 1999; // Ensure proper layering
tutorialOverlay.interactive = true; // Always interactive to block clicks
// Define tutorial steps
self.initializeTutorialSteps = function () {
self.tutorialSteps = [{
id: 'welcome',
title: 'BIENVENIDO A WIZARD DEFENDER!',
description: 'Eres un poderoso mago que debe defender su castillo. Toca directamente sobre los enemigos para atacarlos con hechizos.',
duration: 3000,
showSkip: true,
highlightElement: 'screen',
waitForTap: true
}, {
id: 'enemies_approach',
title: 'ENEMIGOS Y MEJORAS',
description: 'Los esqueletos vienen por 5 caminos diferentes. Toca directamente sobre cada enemigo para atacarlo. Gana monedas y desbloquea mejoras cada 12 enemigos.',
duration: 3000,
spawnDemoEnemy: true,
highlightElement: 'coinCounter'
}, {
id: 'tutorial_complete',
title: 'TUTORIAL COMPLETADO!',
description: 'Recuerda: toca directamente sobre los enemigos para atacarlos. Cuida tu salud y sobrevive el mayor tiempo posible. ¡Buena suerte!',
duration: 3000,
startGame: true,
highlightElement: 'healthBar'
}];
};
// Start the tutorial
self.startTutorial = function () {
// Always show tutorial when explicitly called
self.isActive = true;
self.currentStep = 0;
// Initialize tutorial steps first
self.initializeTutorialSteps();
// Make tutorial visible and properly layered
self.visible = true;
self.zIndex = 2000;
// Configure tutorial overlay properly
tutorialOverlay.visible = true;
tutorialOverlay.alpha = 0.8;
tutorialOverlay.tint = 0x000000;
tutorialOverlay.interactive = true; // Block clicks to game below
tutorialOverlay.zIndex = 1999; // Ensure proper layering
// Hide game menu while tutorial is active
if (gameMenu) {
gameMenu.visible = false;
}
// Hide all game elements during tutorial
if (wizard) wizard.visible = false;
if (backgroundMap) backgroundMap.visible = false;
coinText.visible = false;
killCountText.visible = false;
tapText.visible = false;
healthBarBg.visible = false;
healthBar.visible = false;
healthText.visible = false;
for (var i = 0; i < paths.length; i++) {
paths[i].visible = false;
}
// Start first step
self.showStep(0);
return true; // Tutorial started
};
// Show a specific tutorial step
self.showStep = function (stepIndex) {
if (stepIndex >= self.tutorialSteps.length) {
self.completeTutorial();
return;
}
// Clear previous step elements
self.clearStepElements();
var step = self.tutorialSteps[stepIndex];
self.currentStep = stepIndex;
// Ensure tutorial is visible and on top
self.visible = true;
tutorialOverlay.visible = true;
self.zIndex = 2000;
// Create step title with proper sizing
var titleText = new Text2(step.title, {
size: 80,
fill: 0xFFD700,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1600
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 900;
self.addChild(titleText);
self.tutorialTexts.push(titleText);
// Create step description with proper text wrapping
var descText = new Text2(step.description, {
size: 60,
fill: 0xFFFFFF,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1800
});
descText.anchor.set(0.5, 0.5);
descText.x = 2048 / 2;
descText.y = 1300;
self.addChild(descText);
self.tutorialTexts.push(descText);
// Handle special step behaviors
if (step.spawnDemoEnemy) {
self.spawnDemoEnemy();
}
if (step.highlightElement) {
self.highlightElement(step.highlightElement);
}
// Show continue button or wait for specific action
if (step.waitForTap) {
var tapPrompt = new Text2('TOCA LA PANTALLA PARA CONTINUAR', {
size: 50,
fill: 0x00FF00,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1400
});
tapPrompt.anchor.set(0.5, 0.5);
tapPrompt.x = 2048 / 2;
tapPrompt.y = 1800;
self.addChild(tapPrompt);
self.tutorialTexts.push(tapPrompt);
// Add pulsing effect to tap prompt
tween(tapPrompt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(tapPrompt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
} else {
// Always show continue prompt - no auto-advance
var continuePrompt = new Text2('TOCA LA PANTALLA PARA CONTINUAR', {
size: 50,
fill: 0x00FF00,
font: "monospace",
wordWrap: true,
wordWrapWidth: 1400
});
continuePrompt.anchor.set(0.5, 0.5);
continuePrompt.x = 2048 / 2;
continuePrompt.y = 1800;
self.addChild(continuePrompt);
self.tutorialTexts.push(continuePrompt);
// Add pulsing effect to continue prompt
tween(continuePrompt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(continuePrompt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
// Show skip button on first step
if (step.showSkip) {
var skipBtn = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 200,
scaleX: 1.5,
scaleY: 1.5
});
skipBtn.tint = 0xFF4444;
self.highlightElements.push(skipBtn);
var skipText = new Text2('OMITIR', {
size: 50,
fill: 0xFFFFFF,
font: "monospace"
});
skipText.anchor.set(0.5, 0.5);
skipText.x = 2048 - 200;
skipText.y = 200;
self.addChild(skipText);
self.tutorialTexts.push(skipText);
}
};
// Highlight specific game elements
self.highlightElement = function (elementType) {
switch (elementType) {
case 'healthBar':
if (healthBarBg && healthBar) {
// Show health UI temporarily
healthBarBg.visible = true;
healthBar.visible = true;
healthText.visible = true;
// Create highlight glow around health bar
var healthGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: 270,
y: 110,
scaleX: 3,
scaleY: 1.5
}));
healthGlow.tint = 0x00FF00;
healthGlow.alpha = 0.6;
self.highlightElements.push(healthGlow);
// Animate glow
tween(healthGlow, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
break;
case 'coinCounter':
if (coinText) {
// Show coin UI temporarily
coinText.visible = true;
coinText.setText('Coins: 15'); // Show example coins
// Create highlight glow around coin counter
var coinGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: 270,
y: 150,
scaleX: 3,
scaleY: 1.5
}));
coinGlow.tint = 0xFFD700;
coinGlow.alpha = 0.6;
self.highlightElements.push(coinGlow);
// Animate glow
tween(coinGlow, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
break;
case 'screen':
// Create large highlight overlay
var screenGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 20,
scaleY: 25
}));
screenGlow.tint = 0x00FFFF;
screenGlow.alpha = 0.2;
self.highlightElements.push(screenGlow);
// Animate screen glow
tween(screenGlow, {
alpha: 0.1
}, {
duration: 1500,
easing: tween.easeInOut
});
// Create arrow pointing to center
self.createArrow(2048 / 2, 2732 / 2 - 400, 2048 / 2, 2732 / 2);
break;
}
};
// Create pointing arrow
self.createArrow = function (fromX, fromY, toX, toY) {
var arrow = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: fromX,
y: fromY,
scaleX: 3,
scaleY: 3
}));
arrow.tint = 0xFFD700;
arrow.alpha = 0.8;
// Calculate arrow direction
var dx = toX - fromX;
var dy = toY - fromY;
var angle = Math.atan2(dy, dx);
arrow.rotation = angle;
// Animate arrow bouncing
tween(arrow, {
x: toX,
y: toY
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(arrow, {
x: fromX,
y: fromY
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
self.arrows.push(arrow);
};
// Spawn a demo enemy for tutorial
self.spawnDemoEnemy = function () {
// Create a slow-moving demo enemy
var demoEnemy = game.addChild(new Enemy());
demoEnemy.x = 2048 / 2;
demoEnemy.y = -100;
demoEnemy.speed = 1; // Very slow for demo
demoEnemy.health = 1;
demoEnemy.maxHealth = 1;
demoEnemy.pathIndex = 0; // Center path
// Make demo enemy more visible
for (var frameIdx = 0; frameIdx < demoEnemy.animationFrames.length; frameIdx++) {
demoEnemy.animationFrames[frameIdx].tint = 0xFF6600; // Orange tint
}
enemies.push(demoEnemy);
// Create highlight around demo enemy
var enemyGlow = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: demoEnemy.x,
y: demoEnemy.y,
scaleX: 4,
scaleY: 4
}));
enemyGlow.tint = 0xFF0000;
enemyGlow.alpha = 0.5;
// Make glow follow enemy
var _glowFollower = function glowFollower() {
if (demoEnemy && demoEnemy.parent && enemyGlow && enemyGlow.parent) {
enemyGlow.x = demoEnemy.x;
enemyGlow.y = demoEnemy.y;
// Continue following
tween({}, {}, {
duration: 60,
onFinish: _glowFollower
});
} else if (enemyGlow && enemyGlow.parent) {
enemyGlow.destroy();
}
};
_glowFollower();
self.highlightElements.push(enemyGlow);
};
// Clear all tutorial step elements
self.clearStepElements = function () {
// Clear tutorial texts
for (var i = 0; i < self.tutorialTexts.length; i++) {
if (self.tutorialTexts[i] && self.tutorialTexts[i].parent) {
self.tutorialTexts[i].destroy();
}
}
self.tutorialTexts = [];
// Clear highlight elements
for (var i = 0; i < self.highlightElements.length; i++) {
if (self.highlightElements[i] && self.highlightElements[i].parent) {
self.highlightElements[i].destroy();
}
}
self.highlightElements = [];
// Clear arrows
for (var i = 0; i < self.arrows.length; i++) {
if (self.arrows[i] && self.arrows[i].parent) {
self.arrows[i].destroy();
}
}
self.arrows = [];
};
// Advance to next tutorial step
self.nextStep = function () {
self.currentStep++;
if (self.currentStep < self.tutorialSteps.length) {
self.showStep(self.currentStep);
} else {
// Tutorial completed naturally (not skipped)
self.skipped = false;
self.completeTutorial();
}
};
// Complete the tutorial
self.completeTutorial = function () {
// Mark tutorial as completed
storage.tutorialCompleted = true;
// Clear all tutorial elements
self.clearStepElements();
// Hide tutorial completely
tutorialOverlay.visible = false;
tutorialOverlay.interactive = false; // Remove interactivity
self.visible = false;
self.isActive = false;
// Only auto-start game if tutorial was completed naturally (not skipped)
if (!self.skipped) {
// Start game immediately without showing menu
if (gameMenu) {
gameMenu.startGame();
}
} else {
// If skipped, show the menu
if (gameMenu) {
gameMenu.visible = true;
}
}
// Reset skipped flag for next time
self.skipped = false;
};
// Handle tutorial interactions
self.down = function (x, y, obj) {
if (!self.isActive) return;
var step = self.tutorialSteps[self.currentStep];
// Handle skip button (top-right corner)
if (step.showSkip && x >= 2048 - 300 && x <= 2048 - 100 && y >= 100 && y <= 300) {
self.skipped = true; // Mark as skipped
self.completeTutorial();
return;
}
// For any tap anywhere on screen, advance to next step
if (step.id === 'tap_to_attack') {
// Simulate spell casting for demo
if (wizard) {
wizard.attack(0); // Attack center path
}
// Wait a moment then advance
tween({}, {}, {
duration: 1000,
onFinish: function onFinish() {
self.nextStep();
}
});
} else {
// For all other steps, advance immediately on any tap
self.nextStep();
}
};
return self;
});
var UnifiedProjectile = Container.expand(function (type) {
var self = Container.call(this);
// Default type
self.projectileType = type || 'projectile';
// Common properties
self.speed = 50;
self.direction = {
x: 0,
y: 0
};
self.lastIntersecting = {};
self.targetEnemy = null;
self.trailSegments = [];
// Get projectile configuration from unified config
var config = GAME_CONFIG.projectiles[self.projectileType];
if (!config) {
console.error('Unknown projectile type:', self.projectileType);
config = GAME_CONFIG.projectiles.projectile; // fallback
}
self.speed = config.speed;
// Create main projectile graphics
if (self.projectileType === 'projectile') {
self.mainGraphics = self.attachAsset(config.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: config.scale,
scaleY: config.scale
});
} else {
self.mainGraphics = self.attachAsset(config.glowAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: config.scale,
scaleY: config.scale
});
}
self.mainGraphics.tint = config.tint;
self.mainGraphics.alpha = 0.9;
// Create glow effect for projectile type
if (self.projectileType === 'projectile') {
var lightGlow = self.attachAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
lightGlow.alpha = 0.15;
lightGlow.tint = config.glowTint;
// Animate the glow effect
tween(lightGlow, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0.05
}, {
duration: 800,
easing: tween.easeInOut
});
tween(lightGlow, {
rotation: Math.PI * 4
}, {
duration: 1000,
easing: tween.linear
});
}
self.update = function () {
// Pause when tutorial is active
if (tutorial && tutorial.isActive) {
return;
}
// Move projectile
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Type-specific visual effects
if (config.hasRotation) {
var angle = Math.atan2(self.direction.y, self.direction.x);
self.mainGraphics.rotation = angle + Math.PI / 2;
}
if (config.hasFlicker) {
var flicker = 1 + Math.sin(LK.ticks * 0.4) * 0.3;
self.mainGraphics.scaleX = config.scale * flicker;
self.mainGraphics.scaleY = config.scale * flicker;
self.mainGraphics.rotation += 0.2;
}
// Remove if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.removeFromGame();
return;
}
// Handle collisions based on type
if (self.projectileType === 'fireBall') {
self.handleFireBallCollisions();
} else {
self.handleSingleTargetCollision();
}
};
self.handleSingleTargetCollision = function () {
if (self.targetEnemy && self.targetEnemy.parent && self.targetEnemy !== wizard) {
if (!self.lastIntersecting.target) {
self.lastIntersecting.target = false;
}
var currentIntersecting = self.intersects(self.targetEnemy);
if (!self.lastIntersecting.target && currentIntersecting) {
var damage = config.damage;
// Spell power upgrade removed - using base damage
self.targetEnemy.takeDamage(damage);
self.removeFromGame();
return;
}
self.lastIntersecting.target = currentIntersecting;
} else {
self.removeFromGame();
}
};
self.handleFireBallCollisions = function () {
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
if (enemy === wizard) {
continue;
}
if (!self.lastIntersecting[i]) {
self.lastIntersecting[i] = false;
}
var currentIntersecting = self.intersects(enemy);
if (!self.lastIntersecting[i] && currentIntersecting) {
enemy.takeDamage(config.damage);
LK.effects.flashObject(enemy, 0xFF4500, 400);
// Create fire explosion effect
var fireEffect = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 3,
scaleY: 3
}));
fireEffect.tint = 0xFF6600;
fireEffect.alpha = 0.8;
tween(fireEffect, {
scaleX: 6,
scaleY: 6,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
fireEffect.destroy();
}
});
// Fire particles
for (var fireIdx = 0; fireIdx < 6; fireIdx++) {
var fireParticle = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 0.6,
scaleY: 0.6
}));
fireParticle.tint = 0xFF4500;
fireParticle.alpha = 0.9;
var fireAngle = fireIdx / 6 * Math.PI * 2;
var fireSpeed = 50;
var fireTargetX = enemy.x + Math.cos(fireAngle) * fireSpeed;
var fireTargetY = enemy.y + Math.sin(fireAngle) * fireSpeed;
tween(fireParticle, {
x: fireTargetX,
y: fireTargetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
fireParticle.destroy();
}
});
}
self.removeFromGame();
return;
}
self.lastIntersecting[i] = currentIntersecting;
}
};
self.removeFromGame = function () {
// Clean up trail segments
for (var i = 0; i < self.trailSegments.length; i++) {
var segment = self.trailSegments[i];
if (segment && segment.parent) {
segment.destroy();
}
}
self.trailSegments = [];
// Remove from projectiles array
for (var i = projectiles.length - 1; i >= 0; i--) {
if (projectiles[i] === self) {
projectiles.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
// UpgradeMenu class removed - using spell deck system instead
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.experience = 0;
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;
}
// Upgrade menu removed - no pause needed
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
// Animation system - cycle through wizard frames
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 new 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
tween(spell, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
spell.destroy();
}
});
// Add rotation animation to spell
tween(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) {
LK.effects.flashObject(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
LK.effects.flashObject(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
tween({}, {}, {
duration: regenTime,
onFinish: function onFinish() {
// Regenerate shield
self.shieldActive = true;
self.currentShieldHits = 0;
// Visual feedback for shield regeneration
LK.effects.flashObject(self, 0x00BFFF, 500);
// Add shield regeneration animation
tween(self.shieldGraphics, {
scaleX: 5,
scaleY: 5,
alpha: 1.0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.shieldGraphics, {
scaleX: 3,
scaleY: 3,
alpha: 0.7
}, {
duration: 400,
easing: tween.easeIn
});
}
});
}
});
}
// No damage taken, shield absorbed it
return;
}
self.health -= damage;
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 = enemies.concat(ogres).concat(knights).concat(miniBosses);
for (var enemyIdx = allEnemies.length - 1; enemyIdx >= 0; enemyIdx--) {
var enemy = allEnemies[enemyIdx];
// Create destruction effect for each enemy
LK.effects.flashObject(enemy, 0xFFD700, 500);
// Create golden explosion particles
for (var particleIdx = 0; particleIdx < 8; particleIdx++) {
var destructionParticle = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 0.5,
scaleY: 0.5
}));
destructionParticle.tint = 0xFFD700;
destructionParticle.alpha = 0.9;
// Random explosion direction
var explosionAngle = particleIdx / 8 * Math.PI * 2;
var explosionSpeed = 60 + Math.random() * 40;
var explosionTargetX = enemy.x + Math.cos(explosionAngle) * explosionSpeed;
var explosionTargetY = enemy.y + Math.sin(explosionAngle) * explosionSpeed;
// Animate explosion particle
tween(destructionParticle, {
x: explosionTargetX,
y: explosionTargetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
destructionParticle.destroy();
}
});
}
// Kill ALL enemies instantly by calling die() method
enemy.die();
}
// Visual effects for revival
LK.effects.flashScreen(0x00FF00, 1500); // Green flash for revival
LK.effects.flashObject(self, 0xFFD700, 1000); // Golden flash on wizard
// Reduced revival particles for better performance
for (var reviveIdx = 0; reviveIdx < 8; reviveIdx++) {
var reviveParticle = game.addChild(LK.getAsset('energySphere', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + (Math.random() - 0.5) * 200,
y: self.y + (Math.random() - 0.5) * 200,
scaleX: 1.0,
scaleY: 1.0
}));
reviveParticle.tint = 0x00FF00;
reviveParticle.alpha = 0.9;
// Animate revival particles toward wizard
tween(reviveParticle, {
x: self.x,
y: self.y,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
reviveParticle.destroy();
}
});
}
// Create healing aura effect
var healingAura = game.addChild(LK.getAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 1,
scaleY: 1
}));
healingAura.tint = 0x00FF00;
healingAura.alpha = 0.8;
// Animate healing aura expansion
tween(healingAura, {
scaleX: 8,
scaleY: 8,
alpha: 0
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
healingAura.destroy();
}
});
// 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
tween(game, {
x: originalX + shakeIntensity,
y: originalY + shakeIntensity * 0.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(game, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
};
self.activateForcePush = function () {
// Visual effect for force push activation
LK.effects.flashScreen(0x8A2BE2, 300); // Purple flash
LK.effects.flashObject(self, 0x8A2BE2, 500); // Purple flash on wizard
// Push back all enemies with improved effects
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
// Calculate direction from wizard to enemy
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Improved force push: stronger push and damage
var pushDistance = upgradeLevels.forcePush > 1 ? 300 : 200; // Stronger push
var pushX = dx / distance * pushDistance;
var pushY = dy / distance * pushDistance;
// Calculate new position
var newX = enemy.x + pushX;
var newY = enemy.y + pushY;
// Ensure enemies don't go off screen
newX = Math.max(50, Math.min(1998, newX));
newY = Math.max(-100, Math.min(2732, newY));
// Animate the push effect
tween(enemy, {
x: newX,
y: newY
}, {
duration: 300,
easing: tween.easeOut
});
// Improved force push: deal damage
if (upgradeLevels.forcePush > 1) {
enemy.takeDamage(50);
}
// Visual effect on each enemy
LK.effects.flashObject(enemy, 0x8A2BE2, 200);
}
}
};
self.activateFreezePulse = function () {
// Visual effect for freeze pulse activation
LK.effects.flashScreen(0x87CEEB, 500); // Light blue flash
LK.effects.flashObject(self, 0x87CEEB, 700); // Light blue flash on wizard
// Play freeze sound effect
LK.getSound('iceFreeze').play();
// Freeze all enemies with improved effects
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
for (var i = 0; i < allEnemies.length; i++) {
var enemy = allEnemies[i];
// Improved freeze: longer duration and damage
var freezeDuration = upgradeLevels.freezePulse > 1 ? 120 : 60; // 2s vs 1s
enemy.frozen = true;
enemy.frozenTimer = freezeDuration;
// Improved freeze: deal damage
if (upgradeLevels.freezePulse > 1) {
enemy.takeDamage(30);
}
// Visual freeze effect - tint enemy light blue
tween(enemy, {
tint: 0x87CEEB
}, {
duration: 100,
easing: tween.easeOut
});
// Reduced ice crystal particles for better performance
if (i % 2 === 0) {
// Only create effects for every other enemy
for (var iceIdx = 0; iceIdx < 3; iceIdx++) {
var iceCrystal = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x + (Math.random() - 0.5) * 60,
y: enemy.y + (Math.random() - 0.5) * 60,
scaleX: 1.0,
scaleY: 1.0
}));
iceCrystal.tint = 0x87CEEB;
iceCrystal.alpha = 0.9;
// Create floating ice effect
tween(iceCrystal, {
y: iceCrystal.y - 30,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
iceCrystal.destroy();
}
});
}
}
// Remove freeze tint after frozen state ends
var visualDuration = upgradeLevels.freezePulse > 1 ? 2000 : 1000;
tween({}, {}, {
duration: visualDuration,
onFinish: function onFinish() {
if (enemy && enemy.parent) {
// Remove freeze tint after frozen effect ends
tween(enemy, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeIn
});
}
}
});
}
};
self.activateThorns = function () {
// Visual effect for thorns activation
LK.effects.flashScreen(0x8B4513, 300); // Brown flash
LK.effects.flashObject(self, 0x8B4513, 500); // Brown flash on wizard
// Find the closest enemy to the wizard
var closestEnemy = null;
var closestDistance = Infinity;
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
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;
}
}
// Only create spikes if there is a closest enemy
if (!closestEnemy) {
LK.getSound('spellCast').play();
return;
}
// Create spikes only along the closest enemy's path
var pathIdx = closestEnemy.pathIndex;
var pathAngle = pathAngles[pathIdx];
// Calculate spawn position for this path (same as enemy spawning)
var spawnX, spawnY;
if (pathIdx === 0) {
// Center path - spawn at top edge
spawnX = 2048 / 2;
spawnY = -100;
} else if (pathIdx === 1) {
// Path 2 - spawn at top right edge
spawnX = 2048 + 50;
spawnY = -50;
} else if (pathIdx === 2) {
// Path 3 - spawn at top left edge
spawnX = -50;
spawnY = -50;
} else if (pathIdx === 3) {
// Path 4 - spawn at left edge
spawnX = -100;
spawnY = 2732 / 2 + 400;
} else if (pathIdx === 4) {
// Path 5 - spawn at right edge
spawnX = 2048 + 100;
spawnY = 2732 / 2 + 400;
}
// Calculate wizard position (same as enemy targeting)
var wizardX = self.x;
var wizardY = self.y;
// Calculate path distance and divide into 3 sections with gaps
var pathDistance = Math.sqrt((spawnX - wizardX) * (spawnX - wizardX) + (spawnY - wizardY) * (spawnY - wizardY));
var sectionLength = pathDistance / 5; // Each section is 1/5 of total path
var gapLength = pathDistance / 10; // Gaps are 1/10 of total path
// Define 3 sections along the path with gaps between them
var sections = [{
start: 0.1,
end: 0.3
},
// First section: 10% to 30% along path
{
start: 0.45,
end: 0.65
},
// Second section: 45% to 65% along path
{
start: 0.8,
end: 1.0
} // Third section: 80% to 100% along path
];
// Create spikes in reverse sequential order: last section, then middle, then first
var sectionOrder = [2, 1, 0]; // Create sections starting from farthest outward toward wizard
for (var orderIdx = 0; orderIdx < sectionOrder.length; orderIdx++) {
var sectionIdx = sectionOrder[orderIdx];
var section = sections[sectionIdx];
var sectionStartDistance = pathDistance * section.start;
var sectionEndDistance = pathDistance * section.end;
var spikeSpacing = 150; // Distance between spikes within each section
// Calculate number of spikes in this section
var sectionLength = sectionEndDistance - sectionStartDistance;
var numSpikesInSection = Math.floor(sectionLength / spikeSpacing);
// Calculate delay for sequential appearance
var baseDelay = orderIdx * 300; // 300ms delay between sections
// Create spikes within this section with sequential timing
for (var s = 0; s < numSpikesInSection; s++) {
var spikeDistanceInSection = s * spikeSpacing + spikeSpacing / 2;
var totalSpikeDistance = sectionStartDistance + spikeDistanceInSection;
var progress = totalSpikeDistance / pathDistance;
var spikeX = spawnX + (wizardX - spawnX) * progress;
var spikeY = spawnY + (wizardY - spawnY) * progress;
// Only create spike if position is within game bounds
if (spikeX >= 0 && spikeX <= 2048 && spikeY >= 0 && spikeY <= 2732) {
// Create spike with delay
(function (delayTime, spikeX, spikeY, pathIdx) {
tween({}, {}, {
duration: delayTime,
onFinish: function onFinish() {
var spike = game.addChild(LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: spikeX,
y: spikeY,
scaleX: 0.1,
scaleY: 0.1
}));
spike.tint = 0x8B4513; // Brown color for thorns
spike.pathIndex = pathIdx;
// Initialize hit tracking for this spike
spike.hitEnemies = [];
// Set spike to be visible immediately
spike.alpha = 1.0;
spike.scaleX = 1.5;
spike.scaleY = 1.5;
// Animate spike emerging from ground
tween(spike, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Spike stays for a moment then disappears
tween(spike, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
spike.destroy();
}
});
}
});
}
});
})(baseDelay + s * 50, spikeX, spikeY, pathIdx); // 50ms delay between spikes in same section
}
}
}
LK.getSound('spellCast').play();
};
self.launchFireBall = function () {
// Visual effect for fire ball launch
LK.effects.flashScreen(0xFF4500, 300); // Orange flash
LK.effects.flashObject(self, 0xFF4500, 500); // Orange flash on wizard
// Create fire ball projectile at wizard position
var fireBall = game.addChild(new UnifiedProjectile('fireBall'));
fireBall.x = self.x;
fireBall.y = self.y;
// Find closest enemy to target
var closestEnemy = null;
var closestDistance = Infinity;
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
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;
}
}
// Set fire ball direction toward closest enemy or default direction
if (closestEnemy) {
var dx = closestEnemy.x - self.x;
var dy = closestEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
fireBall.direction.x = dx / distance;
fireBall.direction.y = dy / distance;
}
} else {
// Default direction upward if no enemies
fireBall.direction.x = 0;
fireBall.direction.y = -1;
}
LK.getSound('spellCast').play();
LK.getSound('fireWhoosh').play();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Black background for pixel art
});
/****
* Game Code
****/
// Unified game configuration object with namespace organization
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);
}
// Main game namespace with organized modules
var GAME = {
// Core game systems
Core: {
// Event system for component communication
Events: {
listeners: {},
emit: function emit(eventName, data) {
if (this.listeners[eventName]) {
for (var i = 0; i < this.listeners[eventName].length; i++) {
var listener = this.listeners[eventName][i];
if (typeof listener === 'function') {
listener(data);
}
}
}
},
on: function on(eventName, callback) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push(callback);
},
off: function off(eventName, callback) {
if (this.listeners[eventName]) {
var index = this.listeners[eventName].indexOf(callback);
if (index !== -1) {
this.listeners[eventName].splice(index, 1);
}
}
}
},
// Simple state management system
State: {
current: 'menu',
history: [],
setState: function setState(newState) {
this.history.push(this.current);
this.current = newState;
GAME.Core.Events.emit('stateChanged', {
from: this.history[this.history.length - 1],
to: newState
});
},
getPreviousState: function getPreviousState() {
return this.history[this.history.length - 1] || 'menu';
}
}
},
// Entity configurations and factories
Entities: {
Enemy: {
skeleton: {
assetPrefix: 'esqueleto',
scale: 3.0,
baseHealth: 100,
baseSpeed: 3,
animationSpeed: 15,
vibration: 50,
damageTextSize: 120,
damageTextColor: 0xFF4444,
impactScale: 1.5,
deathRotation: Math.PI * 0.5,
scoreReward: 10,
startThreshold: 0,
coinReward: 1,
damage: 20,
spawnInterval: {
FACIL: {
base: 120,
min: 60,
scaling: 5
},
NORMAL: {
base: 90,
min: 40,
scaling: 6
},
DIFICIL: {
base: 90,
min: 40,
scaling: 6
}
},
arrayName: 'enemies',
maxOnScreen: 8,
pathPreference: 'center',
eliteChance: 0.20,
soundOnSpawn: 0.3
},
ogre: {
assetPrefix: 'ogre',
scale: 3.0,
baseHealth: 200,
baseSpeed: 2.5,
animationSpeed: 20,
vibration: 75,
damageTextSize: 130,
damageTextColor: 0xFF6600,
impactScale: 2.0,
deathRotation: Math.PI * 1.2,
scoreReward: 15,
startThreshold: 15,
coinReward: 1,
damage: 30,
spawnInterval: {
FACIL: 240,
NORMAL: 180,
DIFICIL: 120
},
arrayName: 'ogres',
maxOnScreen: 4,
pathPreference: 'outer',
eliteChance: 0.15,
resistances: ['ice'],
specialAbilities: ['charge']
},
knight: {
assetPrefix: 'knight',
scale: 3.0,
baseHealth: 300,
baseSpeed: 2,
animationSpeed: 22,
vibration: 100,
damageTextSize: 140,
damageTextColor: 0xFFD700,
impactScale: 1.8,
deathRotation: Math.PI * 0.8,
scoreReward: 20,
startThreshold: {
FACIL: 40,
NORMAL: 30,
DIFICIL: 20
},
coinReward: 1,
damage: 40,
spawnInterval: {
FACIL: 420,
NORMAL: 300,
DIFICIL: 240
},
arrayName: 'knights',
maxOnScreen: 3,
pathPreference: 'any',
eliteChance: 0.10,
armor: 50,
resistances: ['physical'],
specialAbilities: ['block', 'counter']
},
miniBoss: {
assetPrefix: 'knight',
scale: 5.0,
baseHealth: 3000,
baseSpeed: 4,
animationSpeed: 12,
vibration: [100, 50, 100],
damageTextSize: 160,
damageTextColor: 0xFF0000,
impactScale: 2.5,
deathRotation: Math.PI * 2,
scoreReward: 100,
tint: 0x8B0000,
startThreshold: 80,
endThreshold: 85,
spawnChance: 0.02,
coinReward: 5,
damage: 75,
arrayName: 'miniBosses',
maxOnScreen: 1,
pathPreference: 'center',
phases: [{
healthThreshold: 0.66,
speedBoost: 1.5,
abilities: ['rage']
}, {
healthThreshold: 0.33,
speedBoost: 2.0,
abilities: ['rage', 'summon']
}],
immunities: ['freeze', 'slow'],
specialAbilities: ['regeneration', 'aoe_attack']
}
},
// Entity configuration factory
createEnemyConfig: function createEnemyConfig(type, overrides) {
var base = this.Enemy[type];
if (!base) {
console.error('Enemy type not found:', type);
return this.Enemy.skeleton; // fallback to skeleton
}
var config = {};
// Deep copy base configuration
for (var key in base) {
if (_typeof(base[key]) === 'object' && base[key] !== null && !Array.isArray(base[key])) {
config[key] = {};
for (var subKey in base[key]) {
config[key][subKey] = base[key][subKey];
}
} else {
config[key] = base[key];
}
}
// Apply overrides
if (overrides) {
for (var key in overrides) {
if (_typeof(overrides[key]) === 'object' && overrides[key] !== null && !Array.isArray(overrides[key])) {
if (!config[key]) config[key] = {};
for (var subKey in overrides[key]) {
config[key][subKey] = overrides[key][subKey];
}
} else {
config[key] = overrides[key];
}
}
}
return config;
}
},
// Configuration management
Config: {
// Path configurations
paths: {
count: 5,
angles: [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6],
spawnPositions: [{
x: 2048 / 2,
y: -100
},
// Center path
{
x: 2048 + 50,
y: -50
},
// Top right
{
x: -50,
y: -50
},
// Top left
{
x: -100,
y: 2732 / 2 + 400
},
// Left edge
{
x: 2048 + 100,
y: 2732 / 2 + 400
} // Right edge
]
},
// Gameplay configurations
gameplay: {
maxEnemiesOnScreen: 15,
pathCooldown: 300,
upgradeMenuTriggers: [12, 35, 50, 70],
baseUpgradeCost: 5,
upgradeCostIncrease: 5,
attackCooldown: 30,
projectileSpeed: 50,
wizardAnimationSpeed: 18,
segmentSize: 120,
difficultyScaling: {
FACIL: {
healthMult: 0.8,
speedMult: 0.9,
bonusCoins: true,
healingChance: 0.15
},
NORMAL: {
healthMult: 1.0,
speedMult: 1.0,
standardRewards: true
},
DIFICIL: {
healthMult: 1.2,
speedMult: 1.1,
eliteEnemies: true,
reducedCoins: true
}
}
},
// Projectile configurations
projectiles: {
projectile: {
assetId: 'projectile',
glowAsset: 'projectileGlow',
scale: 1.5,
speed: 50,
damage: 100,
tint: 0x44aaff,
glowTint: 0x44aaff,
hasTrail: false,
hasRotation: true
},
energyBeam: {
assetId: 'projectileGlow',
glowAsset: 'projectileGlow',
scale: 1.0,
speed: 60,
damage: 100,
tint: 0x00ffff,
glowTint: 0x00ffff,
hasTrail: false,
hasRotation: true
},
fireBall: {
assetId: 'projectileGlow',
glowAsset: 'projectileGlow',
scale: 1.5,
speed: 40,
damage: 150,
tint: 0xFF4500,
glowTint: 0xFF6600,
hasTrail: true,
hasRotation: true,
hasFlicker: true
}
}
},
// UI management
UI: {
positions: {
healthBar: {
x: 120,
y: 20,
scaleX: 1.0,
scaleY: 1.0
},
healthBarBg: {
x: 120,
y: 20,
scaleX: 1.0,
scaleY: 1.0
},
coinText: {
x: 120,
y: 90,
size: 60,
fill: 0xFFD700,
font: "monospace"
},
killText: {
x: 120,
y: 150,
size: 60,
fill: 0xFF6B6B,
font: "monospace"
},
healthText: {
x: 120,
y: 50,
size: 50,
fill: 0xFFFFFF,
font: "monospace"
},
tapText: {
size: 100,
yOffset: -200,
fill: 0xFF6B6B,
font: "monospace"
},
manaBar: {
x: -300,
y: 20,
scaleX: 2,
scaleY: 0.5
},
manaBarBg: {
x: -300,
y: 20,
scaleX: 2,
scaleY: 0.5
},
manaText: {
x: -300,
y: 50,
size: 40,
fill: 0x4169E1,
font: "monospace"
},
spellSlots: {
startX: -200,
spacing: 100,
y: -100,
scaleX: 1.5,
scaleY: 1.5,
count: 5
},
miniBossHealthBar: {
x: 2048 / 2,
y: 200,
bgScaleX: 1.0,
bgScaleY: 1.0,
fgScaleX: 1.0,
fgScaleY: 1.0,
textSize: 40,
textY: 150
}
}
}
};
// Backward compatibility aliases
var GAME_CONFIG = {
events: GAME.Core.Events,
state: GAME.Core.State,
enemies: GAME.Entities.Enemy,
createEnemyConfig: GAME.Entities.createEnemyConfig,
paths: GAME.Config.paths,
ui: GAME.UI.positions,
gameplay: GAME.Config.gameplay,
projectiles: GAME.Config.projectiles
};
// Game state variables
var gameStarted = false;
var gameMenu;
// Upgrade menu variables removed
var selectedEnemy = null; // Track currently selected enemy for projectile targeting
var energySphere = null; // Track energy sphere instance
// Upgrade system removed - using spell deck system instead
// Upgrade system removed - using spell deck system instead
// Game arrays to track objects
var enemies = [];
var ogres = [];
var knights = [];
var miniBosses = [];
var coins = [];
var projectiles = [];
// Removed combo system variables
// 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 active spell deck system
var activeSpellDeck = new SpellDeck();
var spellSlots = [];
var manaBar, manaBarBg, manaText;
// Create spell UI
function createSpellUI() {
// Mana bar
manaBarBg = LK.getAsset('manaBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_CONFIG.ui.manaBarBg.scaleX,
scaleY: GAME_CONFIG.ui.manaBarBg.scaleY
});
LK.gui.topRight.addChild(manaBarBg);
manaBarBg.x = GAME_CONFIG.ui.manaBarBg.x;
manaBarBg.y = GAME_CONFIG.ui.manaBarBg.y;
manaBarBg.visible = false;
manaBar = LK.getAsset('manaBar', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_CONFIG.ui.manaBar.scaleX,
scaleY: GAME_CONFIG.ui.manaBar.scaleY
});
LK.gui.topRight.addChild(manaBar);
manaBar.x = GAME_CONFIG.ui.manaBar.x;
manaBar.y = GAME_CONFIG.ui.manaBar.y;
manaBar.visible = false;
manaText = new Text2('Mana: 100/100', {
size: GAME_CONFIG.ui.manaText.size,
fill: GAME_CONFIG.ui.manaText.fill,
font: GAME_CONFIG.ui.manaText.font
});
manaText.anchor.set(0, 0);
LK.gui.topRight.addChild(manaText);
manaText.x = GAME_CONFIG.ui.manaText.x;
manaText.y = GAME_CONFIG.ui.manaText.y;
manaText.visible = false;
// Spell slots
for (var i = 0; i < GAME_CONFIG.ui.spellSlots.count; i++) {
var slot = LK.getAsset('spellSlot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: GAME_CONFIG.ui.spellSlots.scaleX,
scaleY: GAME_CONFIG.ui.spellSlots.scaleY
});
LK.gui.bottom.addChild(slot);
slot.x = GAME_CONFIG.ui.spellSlots.startX + i * GAME_CONFIG.ui.spellSlots.spacing;
slot.y = GAME_CONFIG.ui.spellSlots.y;
slot.visible = false;
slot.slotIndex = i;
// Add spell icon if spell exists
if (i < activeSpellDeck.currentDeck.length) {
var spellId = activeSpellDeck.currentDeck[i];
var spell = activeSpellDeck.getSpell(spellId);
if (spell) {
var spellIcon = LK.getAsset('spell', {
anchorX: 0.5,
anchorY: 0.5
});
slot.addChild(spellIcon);
spellIcon.tint = activeSpellDeck.getRarityColor(spell.rarity);
spellIcon.scaleX = 0.8;
spellIcon.scaleY = 0.8;
slot.spellId = spellId;
slot.spellIcon = spellIcon;
}
}
// Add click handler for spell casting
slot.down = function (x, y, obj) {
if (obj.spellId && activeSpellDeck.canCastSpell(obj.spellId)) {
// Cast spell at wizard position or target
var spell = activeSpellDeck.getSpell(obj.spellId);
if (spell) {
var targetX = wizard.x;
var targetY = wizard.y - 100;
// Find nearest enemy for targeted spells
if (spell.id === 'fireball' || spell.id === 'iceShard' || spell.id === 'lightning') {
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var e = 0; e < allEnemies.length; e++) {
var enemy = allEnemies[e];
var dx = enemy.x - wizard.x;
var dy = enemy.y - wizard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
if (nearestEnemy) {
targetX = nearestEnemy.x;
targetY = nearestEnemy.y;
}
}
activeSpellDeck.castSpell(obj.spellId, targetX, targetY);
LK.effects.flashObject(obj, 0x00FF00, 200);
}
} else {
LK.effects.flashObject(obj, 0xFF0000, 200);
}
};
spellSlots.push(slot);
}
}
// Update mana bar
function updateManaBar() {
if (!manaBar || !manaText) return;
var manaPercent = activeSpellDeck.currentMana / activeSpellDeck.maxMana;
manaBar.scaleX = manaPercent * 2;
manaText.setText('Mana: ' + Math.floor(activeSpellDeck.currentMana) + '/' + activeSpellDeck.maxMana);
}
// Update spell slot cooldowns
function updateSpellSlots() {
for (var i = 0; i < spellSlots.length; i++) {
var slot = spellSlots[i];
if (slot.spellId) {
var spell = activeSpellDeck.getSpell(slot.spellId);
if (spell) {
var cooldownEnd = activeSpellDeck.spellCooldowns[slot.spellId] || 0;
var isOnCooldown = cooldownEnd > LK.ticks;
// Update visual state
if (isOnCooldown) {
slot.alpha = 0.5;
// Show cooldown overlay
if (!slot.cooldownOverlay) {
slot.cooldownOverlay = LK.getAsset('cooldownOverlay', {
anchorX: 0.5,
anchorY: 0.5
});
slot.addChild(slot.cooldownOverlay);
slot.cooldownOverlay.alpha = 0.7;
}
// Update cooldown progress
var remaining = cooldownEnd - LK.ticks;
var total = spell.cooldown / 1000 * 60;
var progress = 1 - remaining / total;
slot.cooldownOverlay.scaleY = 1 - progress;
} else {
slot.alpha = 1.0;
if (slot.cooldownOverlay) {
slot.cooldownOverlay.destroy();
slot.cooldownOverlay = null;
}
}
}
}
}
}
// Initialize spell UI
createSpellUI();
// 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();
}
});
}
});
}
// Upgrade system removed - using spell deck system instead
// 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 = GAME_CONFIG.paths.spawnPositions;
for (var p = 0; p < GAME_CONFIG.paths.count; p++) {
var angle = GAME_CONFIG.paths.angles[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 = GAME_CONFIG.gameplay.segmentSize;
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
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: GAME_CONFIG.ui.coinText.size,
fill: GAME_CONFIG.ui.coinText.fill,
font: GAME_CONFIG.ui.coinText.font
});
coinText.anchor.set(0, 0);
LK.gui.topLeft.addChild(coinText);
coinText.x = GAME_CONFIG.ui.coinText.x;
coinText.y = GAME_CONFIG.ui.coinText.y;
coinText.visible = false;
var killCountText = new Text2('Puntuacion: 0', {
size: GAME_CONFIG.ui.killText.size,
fill: GAME_CONFIG.ui.killText.fill,
font: GAME_CONFIG.ui.killText.font
});
killCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(killCountText);
killCountText.x = GAME_CONFIG.ui.killText.x;
killCountText.y = GAME_CONFIG.ui.killText.y;
killCountText.visible = false;
var tapText = new Text2('TAP ENEMIES TO ATTACK!', {
size: GAME_CONFIG.ui.tapText.size,
fill: GAME_CONFIG.ui.tapText.fill,
font: GAME_CONFIG.ui.tapText.font
});
tapText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tapText);
tapText.y = GAME_CONFIG.ui.tapText.yOffset;
tapText.visible = false;
// Health bar UI
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_CONFIG.ui.healthBarBg.scaleX,
scaleY: GAME_CONFIG.ui.healthBarBg.scaleY
});
LK.gui.topLeft.addChild(healthBarBg);
healthBarBg.x = GAME_CONFIG.ui.healthBarBg.x;
healthBarBg.y = GAME_CONFIG.ui.healthBarBg.y;
healthBarBg.visible = false;
var healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_CONFIG.ui.healthBar.scaleX,
scaleY: GAME_CONFIG.ui.healthBar.scaleY
});
LK.gui.topLeft.addChild(healthBar);
healthBar.x = GAME_CONFIG.ui.healthBar.x;
healthBar.y = GAME_CONFIG.ui.healthBar.y;
healthBar.visible = false;
var healthText = new Text2('Health: 100/100', {
size: GAME_CONFIG.ui.healthText.size,
fill: GAME_CONFIG.ui.healthText.fill,
font: GAME_CONFIG.ui.healthText.font
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = GAME_CONFIG.ui.healthText.x;
healthText.y = GAME_CONFIG.ui.healthText.y;
healthText.visible = false;
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 enemy spawn system with precomputed intervals
var SPAWN_SYSTEM = {
timers: {
skeleton: 0,
ogre: 0,
knight: 0,
miniBoss: 0
},
intervals: {},
updateIntervals: function updateIntervals(difficulty, difficultyLevel) {
var skeletonConfig = GAME_CONFIG.enemies.skeleton.spawnInterval[difficulty];
var ogreConfig = GAME_CONFIG.enemies.ogre.spawnInterval[difficulty];
var knightConfig = GAME_CONFIG.enemies.knight.spawnInterval[difficulty];
// Calculate skeleton spawn interval with scaling
this.intervals.skeleton = Math.max(skeletonConfig.min, skeletonConfig.base - difficultyLevel * skeletonConfig.scaling);
// Static intervals for other enemy types
this.intervals.ogre = ogreConfig;
this.intervals.knight = knightConfig;
this.intervals.miniBoss = 60; // Check every second for miniboss
},
canSpawn: function canSpawn(type, totalEnemies) {
var config = GAME_CONFIG.enemies[type];
if (!config) return false;
var difficulty = storage.difficulty || 'NORMAL';
var maxOnScreen = config.maxOnScreen || 15;
switch (type) {
case 'skeleton':
return this.timers.skeleton >= this.intervals.skeleton && miniBosses.length === 0 && totalEnemies < GAME_CONFIG.gameplay.maxEnemiesOnScreen && enemies.length < maxOnScreen;
case 'ogre':
return this.timers.ogre >= this.intervals.ogre && enemyKillCounter >= config.startThreshold && miniBosses.length === 0 && ogres.length < maxOnScreen;
case 'knight':
var threshold = _typeof(config.startThreshold) === 'object' ? config.startThreshold[difficulty] : config.startThreshold;
return this.timers.knight >= this.intervals.knight && enemyKillCounter >= threshold && miniBosses.length === 0 && knights.length < maxOnScreen;
case 'miniBoss':
return this.timers.miniBoss >= this.intervals.miniBoss && enemyKillCounter >= config.startThreshold && enemyKillCounter <= config.endThreshold && miniBosses.length === 0 && Math.random() < config.spawnChance;
}
return false;
},
resetTimer: function resetTimer(type) {
this.timers[type] = 0;
},
incrementTimers: function incrementTimers() {
for (var type in this.timers) {
this.timers[type]++;
}
}
};
// Game input handling
game.down = function (x, y, obj) {
// Tap-to-attack is now handled directly by individual enemies
// No need for path-based attacks since enemies handle their own tap events
};
// Unified impact effect function for all enemy types
function createImpactEffect(enemy, config) {
// Use enemy's type config if no custom config provided
if (!config) config = enemy.typeConfig || {};
// Create impact effect
var impactEffect = game.addChild(LK.getAsset('projectileGlow', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 0.3,
scaleY: 0.3
}));
impactEffect.tint = 0xFFAA00;
impactEffect.alpha = 1.0;
tween(impactEffect, {
scaleX: config.impactScale || 1.5,
scaleY: config.impactScale || 1.5,
alpha: 0
}, {
duration: 450,
easing: tween.easeOut,
onFinish: function onFinish() {
impactEffect.destroy();
}
});
}
// Unified death animation function for all enemy types
function createEnemyDeathAnimation(enemy, enemyType, enemyArray) {
enemy.animationState = 'dying';
enemy.currentFrame = 3;
LK.getSound('painSound').play();
enemy.isDying = true;
// Get type-specific configuration
var deathRotation = Math.PI * 0.5; // Default rotation
var numCoins = 1; // Default coin count
if (enemyType === 'skeleton') {
deathRotation = Math.PI * 0.5;
numCoins = 1;
} else if (enemyType === 'ogre') {
deathRotation = Math.PI * 1.2;
numCoins = 1;
} else if (enemyType === 'knight') {
deathRotation = Math.PI * 0.8;
numCoins = 1;
} else if (enemyType === 'miniBoss') {
deathRotation = Math.PI * 2;
numCoins = 5;
// Clean up mini boss health bar
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();
}
}
// Unified death animation
tween(enemy, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: deathRotation
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Handle coin drops
for (var coinIdx = 0; coinIdx < numCoins; coinIdx++) {
var coin = game.addChild(new Coin());
coin.x = enemy.x + (enemyType === 'miniBoss' ? (Math.random() - 0.5) * 200 : 0);
coin.y = enemy.y - 50 + (enemyType === 'miniBoss' ? (Math.random() - 0.5) * 100 : 0);
coin.isAnimating = true;
coins.push(coin);
var coinTargetX = 120 + coinText.width / 2;
var coinTargetY = 90 + coinText.height / 2;
tween(coin, {
x: coinTargetX,
y: coinTargetY,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000 + (enemyType === 'miniBoss' ? coinIdx * 200 : 0),
easing: tween.easeOut,
onFinish: function onFinish() {
var coinReward = enemyType === 'miniBoss' ? 10 : 1;
var selectedDifficulty = storage.difficulty || 'NORMAL';
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);
if (selectedDifficulty === 'FACIL' && Math.random() < 0.15) {
wizard.health = Math.min(wizard.health + 5, wizard.maxHealth);
updateHealthBar();
LK.effects.flashObject(wizard, 0x00FF00, 200);
}
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === coin) {
coins.splice(i, 1);
break;
}
}
coin.destroy();
}
});
}
// Handle kill counter and progression
var killIncrement = enemyType === 'miniBoss' ? 10 : 1;
enemyKillCounter += killIncrement;
killCountText.setText('Puntuacion: ' + enemyKillCounter);
// Removed path combo system
wizard.gainExperience(enemyType === 'miniBoss' ? 250 : 25);
// Life drain upgrade removed
if (selectedEnemy === enemy) {
selectedEnemy = null;
}
// Remove from appropriate array
for (var i = enemyArray.length - 1; i >= 0; i--) {
if (enemyArray[i] === enemy) {
enemyArray.splice(i, 1);
break;
}
}
enemy.destroy();
LK.setScore(LK.getScore() + enemy.typeConfig.scoreReward);
}
});
}
// 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
// Apply unique difficulty modifiers
var currentSpawnRate, enemyHealthMultiplier, enemySpeedMultiplier;
var specialMechanics = {};
if (selectedDifficulty === 'FACIL') {
// EASY: Slower enemies, less health, longer spawn intervals
currentSpawnRate = Math.max(60, 120 - difficultyLevel * 5); // Much slower spawning
enemyHealthMultiplier = 1; // No health scaling over time
enemySpeedMultiplier = 1 + difficultyLevel * 0.20; // 20% speed increase per level
specialMechanics.bonusCoins = true; // 50% more coins
specialMechanics.healingChance = 0.15; // 15% chance to heal 5 HP on enemy kill
} else if (selectedDifficulty === 'NORMAL') {
// NORMAL: Balanced progression
currentSpawnRate = Math.max(40, 90 - difficultyLevel * 6); // Standard spawning
enemyHealthMultiplier = 1; // No health scaling over time
enemySpeedMultiplier = 1 + difficultyLevel * 0.30; // 30% speed increase per level
specialMechanics.standardRewards = true;
} else if (selectedDifficulty === 'DIFICIL') {
// HARD: Faster enemies, more health, shorter spawn intervals, special enemy abilities
currentSpawnRate = Math.max(20, 60 - difficultyLevel * 4); // Much faster spawning
enemyHealthMultiplier = 1; // No health scaling over time
enemySpeedMultiplier = 1 + difficultyLevel * 0.50; // 50% speed increase per level
specialMechanics.eliteEnemies = true; // 20% chance for elite enemies with double stats
specialMechanics.aggressiveAI = true; // Enemies move more directly toward wizard
specialMechanics.reducedCoins = true; // 25% fewer coins
}
// Unified enemy factory with optimized creation
var EnemyFactory = {
spawnPositions: GAME_CONFIG.paths.spawnPositions,
arrays: {
skeleton: enemies,
ogre: ogres,
knight: knights,
miniBoss: miniBosses
},
createAndSpawn: function createAndSpawn(enemyType, difficulty, difficultyLevel, pathIndex) {
var baseConfig = GAME_CONFIG.enemies[enemyType];
if (!baseConfig) return null;
// Calculate stats once
var config = this.calculateStats(baseConfig, difficulty, difficultyLevel);
var enemy = new Enemy(enemyType, config);
// Set position and properties
this.setupEnemy(enemy, pathIndex || this.selectPath(enemyType));
// Add to game and array
game.addChild(enemy);
this.arrays[enemyType].push(enemy);
// Special handling
this.handleSpecialSpawn(enemy, enemyType, difficulty, difficultyLevel);
return enemy;
},
calculateStats: function calculateStats(baseConfig, difficulty, difficultyLevel) {
// Use centralized difficulty multipliers from GAME_CONFIG
var diffMultipliers = GAME_CONFIG.gameplay.difficultyScaling[difficulty] || GAME_CONFIG.gameplay.difficultyScaling.NORMAL;
var config = {
animationSpeed: baseConfig.animationSpeed,
vibration: baseConfig.vibration,
damageTextSize: baseConfig.damageTextSize,
damageTextColor: baseConfig.damageTextColor,
impactScale: baseConfig.impactScale,
deathRotation: baseConfig.deathRotation,
scoreReward: baseConfig.scoreReward,
coinReward: baseConfig.coinReward,
damage: baseConfig.damage,
tint: baseConfig.tint,
scale: baseConfig.scale,
assetPrefix: baseConfig.assetPrefix
};
// Apply difficulty-based health and speed modifications
var healthMult = diffMultipliers.healthMult || 1.0;
var speedMult = diffMultipliers.speedMult || 1.0;
config.health = Math.floor(baseConfig.baseHealth * healthMult);
config.maxHealth = config.health;
config.speed = baseConfig.baseSpeed * speedMult * (1 + difficultyLevel * (difficulty === 'DIFICIL' ? 0.5 : difficulty === 'NORMAL' ? 0.3 : 0.2));
return config;
},
setupEnemy: function setupEnemy(enemy, pathIndex) {
enemy.pathIndex = pathIndex;
enemy.pathAngle = pathAngles[pathIndex];
var spawnPos = this.spawnPositions[pathIndex];
if (spawnPos) {
enemy.x = Math.max(50, Math.min(1998, spawnPos.x));
enemy.y = Math.max(-200, Math.min(2732 + 100, spawnPos.y));
enemy.lastX = enemy.x;
}
},
selectPath: function selectPath(enemyType) {
if (enemyType === 'skeleton' && enemyKillCounter < 5) return 0;
if (enemyType === 'miniBoss') return 0;
// Smart path selection to avoid overcrowding
var availablePaths = [];
for (var i = 0; i < 5; i++) {
if (pathConsecutiveSpawns[i] < 2) availablePaths.push(i);
}
if (availablePaths.length === 0) {
for (var i = 0; i < 5; i++) pathConsecutiveSpawns[i] = 0;
availablePaths = [0, 1, 2, 3, 4];
}
return availablePaths[Math.floor(Math.random() * availablePaths.length)];
},
handleSpecialSpawn: function handleSpecialSpawn(enemy, enemyType, difficulty, difficultyLevel) {
if (enemyType === 'miniBoss') {
enemy.y = -200;
enemy.createHealthBar();
enemy.updateHealthBar();
LK.effects.flashScreen(0x8B0000, 1000);
} else if (enemyType === 'skeleton' && Math.random() < 0.3) {
LK.getSound('enemyGrowl').play();
}
// Elite enhancement for hard difficulty
if (difficulty === 'DIFICIL' && enemyKillCounter >= 20) {
var eliteChance = enemyType === 'skeleton' ? 0.20 : enemyType === 'ogre' ? 0.15 : 0.10;
if (Math.random() < eliteChance) {
this.makeElite(enemy, enemyType);
}
}
},
makeElite: function makeElite(enemy, enemyType) {
var config = GAME_CONFIG.enemies[enemyType];
if (!config) return;
// Elite multipliers based on enemy type configuration
var eliteMultipliers = {
skeleton: {
health: 2.0,
speed: 1.3,
color: 0xFF6600
},
ogre: {
health: 1.5,
speed: 1.4,
color: 0xFF0000
},
knight: {
health: 2.0,
speed: 1.5,
color: 0xFFD700
},
miniBoss: {
health: 1.2,
speed: 1.2,
color: 0x8B0000
}
};
var multiplier = eliteMultipliers[enemyType] || eliteMultipliers.skeleton;
enemy.health *= multiplier.health;
enemy.speed *= multiplier.speed;
enemy.maxHealth = enemy.health;
enemy.isElite = true;
// Apply elite visual effects
for (var i = 0; i < enemy.animationFrames.length; i++) {
enemy.animationFrames[i].tint = multiplier.color;
}
}
};
// Helper functions now integrated into EnemyFactory
// Efficient unified enemy spawning system
var totalEnemies = enemies.length + ogres.length + knights.length + miniBosses.length;
// Update spawn intervals only when difficulty changes
SPAWN_SYSTEM.updateIntervals(selectedDifficulty, difficultyLevel);
SPAWN_SYSTEM.incrementTimers();
// Reset path cooldowns efficiently
for (var pathIdx = 0; pathIdx < 5; pathIdx++) {
if (pathLastSpawnTime[pathIdx] !== -1 && LK.ticks - pathLastSpawnTime[pathIdx] > pathCooldownDuration) {
pathConsecutiveSpawns[pathIdx] = 0;
}
}
// Spawn enemies using unified system
var enemyTypes = ['skeleton', 'ogre', 'knight', 'miniBoss'];
for (var i = 0; i < enemyTypes.length; i++) {
var enemyType = enemyTypes[i];
if (SPAWN_SYSTEM.canSpawn(enemyType, totalEnemies)) {
var enemy = EnemyFactory.createAndSpawn(enemyType, selectedDifficulty, difficultyLevel);
if (enemy) {
SPAWN_SYSTEM.resetTimer(enemyType);
// Update path tracking for skeleton spawns
if (enemyType === 'skeleton') {
var pathIndex = enemy.pathIndex;
pathConsecutiveSpawns[pathIndex]++;
pathLastSpawnTime[pathIndex] = LK.ticks;
lastSpawnedPath = pathIndex;
consecutiveSpawns = pathConsecutiveSpawns[pathIndex];
}
}
}
}
// Unified collision detection function for all enemy types
function checkAllEnemyCollisions() {
// Combine all enemy arrays into one unified array with type information
var allEnemies = [];
// Add all enemies with their respective damage and behavior
enemies.forEach(function (enemy) {
allEnemies.push({
enemy: enemy,
damage: 20,
removeOnHit: true,
name: 'skeleton',
array: enemies
});
});
ogres.forEach(function (enemy) {
allEnemies.push({
enemy: enemy,
damage: 30,
removeOnHit: true,
name: 'ogre',
array: ogres
});
});
knights.forEach(function (enemy) {
allEnemies.push({
enemy: enemy,
damage: 40,
removeOnHit: true,
name: 'knight',
array: knights
});
});
miniBosses.forEach(function (enemy) {
allEnemies.push({
enemy: enemy,
damage: 75,
removeOnHit: false,
name: 'miniBoss',
array: miniBosses
});
});
// Process all enemies in a single loop
for (var i = allEnemies.length - 1; i >= 0; i--) {
var enemyData = allEnemies[i];
var enemy = enemyData.enemy;
var config = enemyData;
// Remove enemies that went off-screen at bottom
if (enemy.y > 2732 + 100) {
// Find and remove from appropriate array
var arrayIndex = config.array.indexOf(enemy);
if (arrayIndex !== -1) {
config.array.splice(arrayIndex, 1);
}
enemy.destroy();
continue;
}
// Initialize lastIntersecting if not set
if (enemy.lastIntersecting === undefined) {
enemy.lastIntersecting = false;
}
// Check for collision transition (only if enemy is not dying)
var currentIntersecting = wizard.intersects(enemy);
if (!enemy.lastIntersecting && currentIntersecting && !enemy.isDying) {
// Damage wizard when enemy touches for the first time
wizard.takeDamage(config.damage);
// Remove enemy after dealing damage (except mini bosses)
if (config.removeOnHit) {
var arrayIndex = config.array.indexOf(enemy);
if (arrayIndex !== -1) {
config.array.splice(arrayIndex, 1);
}
enemy.destroy();
continue;
}
}
// Update last intersecting state
enemy.lastIntersecting = currentIntersecting;
}
}
// 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 = enemies.concat(ogres).concat(knights).concat(miniBosses);
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 deck system
if (activeSpellDeck && gameStarted) {
activeSpellDeck.updateMana();
activeSpellDeck.updateCombo();
updateManaBar();
updateSpellSlots();
}
// Update time slow effects on enemies
var allEnemies = enemies.concat(ogres).concat(knights).concat(miniBosses);
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);
// Check for spell unlocks
checkSpellUnlocks();
};
// Remove tap text after 5 seconds
tween({}, {}, {
duration: 5000,
onFinish: function onFinish() {
if (tapText && tapText.parent) {
tapText.destroy();
}
}
}); ===================================================================
--- original.js
+++ change.js
@@ -3450,393 +3450,429 @@
/****
* Game Code
****/
-// Unified game configuration object
+// Unified game configuration object with namespace organization
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);
}
-var GAME_CONFIG = {
- // Event system for component communication
- events: {
- listeners: {},
- emit: function emit(eventName, data) {
- if (this.listeners[eventName]) {
- for (var i = 0; i < this.listeners[eventName].length; i++) {
- var listener = this.listeners[eventName][i];
- if (typeof listener === 'function') {
- listener(data);
+// Main game namespace with organized modules
+var GAME = {
+ // Core game systems
+ Core: {
+ // Event system for component communication
+ Events: {
+ listeners: {},
+ emit: function emit(eventName, data) {
+ if (this.listeners[eventName]) {
+ for (var i = 0; i < this.listeners[eventName].length; i++) {
+ var listener = this.listeners[eventName][i];
+ if (typeof listener === 'function') {
+ listener(data);
+ }
}
}
+ },
+ on: function on(eventName, callback) {
+ if (!this.listeners[eventName]) {
+ this.listeners[eventName] = [];
+ }
+ this.listeners[eventName].push(callback);
+ },
+ off: function off(eventName, callback) {
+ if (this.listeners[eventName]) {
+ var index = this.listeners[eventName].indexOf(callback);
+ if (index !== -1) {
+ this.listeners[eventName].splice(index, 1);
+ }
+ }
}
},
- on: function on(eventName, callback) {
- if (!this.listeners[eventName]) {
- this.listeners[eventName] = [];
+ // Simple state management system
+ State: {
+ current: 'menu',
+ history: [],
+ setState: function setState(newState) {
+ this.history.push(this.current);
+ this.current = newState;
+ GAME.Core.Events.emit('stateChanged', {
+ from: this.history[this.history.length - 1],
+ to: newState
+ });
+ },
+ getPreviousState: function getPreviousState() {
+ return this.history[this.history.length - 1] || 'menu';
}
- this.listeners[eventName].push(callback);
- },
- off: function off(eventName, callback) {
- if (this.listeners[eventName]) {
- var index = this.listeners[eventName].indexOf(callback);
- if (index !== -1) {
- this.listeners[eventName].splice(index, 1);
- }
- }
}
},
- // Simple state management system
- state: {
- current: 'menu',
- skeleton: {
- assetPrefix: 'esqueleto',
- scale: 3.0,
- baseHealth: 100,
- baseSpeed: 3,
- animationSpeed: 15,
- vibration: 50,
- damageTextSize: 120,
- damageTextColor: 0xFF4444,
- impactScale: 1.5,
- deathRotation: Math.PI * 0.5,
- scoreReward: 10,
- startThreshold: 0,
- coinReward: 1,
- damage: 20,
- spawnInterval: {
- FACIL: {
- base: 120,
- min: 60,
- scaling: 5
+ // Entity configurations and factories
+ Entities: {
+ Enemy: {
+ skeleton: {
+ assetPrefix: 'esqueleto',
+ scale: 3.0,
+ baseHealth: 100,
+ baseSpeed: 3,
+ animationSpeed: 15,
+ vibration: 50,
+ damageTextSize: 120,
+ damageTextColor: 0xFF4444,
+ impactScale: 1.5,
+ deathRotation: Math.PI * 0.5,
+ scoreReward: 10,
+ startThreshold: 0,
+ coinReward: 1,
+ damage: 20,
+ spawnInterval: {
+ FACIL: {
+ base: 120,
+ min: 60,
+ scaling: 5
+ },
+ NORMAL: {
+ base: 90,
+ min: 40,
+ scaling: 6
+ },
+ DIFICIL: {
+ base: 90,
+ min: 40,
+ scaling: 6
+ }
},
- NORMAL: {
- base: 90,
- min: 40,
- scaling: 6
+ arrayName: 'enemies',
+ maxOnScreen: 8,
+ pathPreference: 'center',
+ eliteChance: 0.20,
+ soundOnSpawn: 0.3
+ },
+ ogre: {
+ assetPrefix: 'ogre',
+ scale: 3.0,
+ baseHealth: 200,
+ baseSpeed: 2.5,
+ animationSpeed: 20,
+ vibration: 75,
+ damageTextSize: 130,
+ damageTextColor: 0xFF6600,
+ impactScale: 2.0,
+ deathRotation: Math.PI * 1.2,
+ scoreReward: 15,
+ startThreshold: 15,
+ coinReward: 1,
+ damage: 30,
+ spawnInterval: {
+ FACIL: 240,
+ NORMAL: 180,
+ DIFICIL: 120
},
- DIFICIL: {
- base: 90,
- min: 40,
- scaling: 6
- }
+ arrayName: 'ogres',
+ maxOnScreen: 4,
+ pathPreference: 'outer',
+ eliteChance: 0.15,
+ resistances: ['ice'],
+ specialAbilities: ['charge']
},
- arrayName: 'enemies',
- maxOnScreen: 8,
- pathPreference: 'center',
- eliteChance: 0.20,
- soundOnSpawn: 0.3
- },
- ogre: {
- assetPrefix: 'ogre',
- scale: 3.0,
- baseHealth: 200,
- baseSpeed: 2.5,
- animationSpeed: 20,
- vibration: 75,
- damageTextSize: 130,
- damageTextColor: 0xFF6600,
- impactScale: 2.0,
- deathRotation: Math.PI * 1.2,
- scoreReward: 15,
- startThreshold: 15,
- coinReward: 1,
- damage: 30,
- spawnInterval: {
- FACIL: 240,
- NORMAL: 180,
- DIFICIL: 120
+ knight: {
+ assetPrefix: 'knight',
+ scale: 3.0,
+ baseHealth: 300,
+ baseSpeed: 2,
+ animationSpeed: 22,
+ vibration: 100,
+ damageTextSize: 140,
+ damageTextColor: 0xFFD700,
+ impactScale: 1.8,
+ deathRotation: Math.PI * 0.8,
+ scoreReward: 20,
+ startThreshold: {
+ FACIL: 40,
+ NORMAL: 30,
+ DIFICIL: 20
+ },
+ coinReward: 1,
+ damage: 40,
+ spawnInterval: {
+ FACIL: 420,
+ NORMAL: 300,
+ DIFICIL: 240
+ },
+ arrayName: 'knights',
+ maxOnScreen: 3,
+ pathPreference: 'any',
+ eliteChance: 0.10,
+ armor: 50,
+ resistances: ['physical'],
+ specialAbilities: ['block', 'counter']
},
- arrayName: 'ogres',
- maxOnScreen: 4,
- pathPreference: 'outer',
- eliteChance: 0.15,
- resistances: ['ice'],
- specialAbilities: ['charge']
+ miniBoss: {
+ assetPrefix: 'knight',
+ scale: 5.0,
+ baseHealth: 3000,
+ baseSpeed: 4,
+ animationSpeed: 12,
+ vibration: [100, 50, 100],
+ damageTextSize: 160,
+ damageTextColor: 0xFF0000,
+ impactScale: 2.5,
+ deathRotation: Math.PI * 2,
+ scoreReward: 100,
+ tint: 0x8B0000,
+ startThreshold: 80,
+ endThreshold: 85,
+ spawnChance: 0.02,
+ coinReward: 5,
+ damage: 75,
+ arrayName: 'miniBosses',
+ maxOnScreen: 1,
+ pathPreference: 'center',
+ phases: [{
+ healthThreshold: 0.66,
+ speedBoost: 1.5,
+ abilities: ['rage']
+ }, {
+ healthThreshold: 0.33,
+ speedBoost: 2.0,
+ abilities: ['rage', 'summon']
+ }],
+ immunities: ['freeze', 'slow'],
+ specialAbilities: ['regeneration', 'aoe_attack']
+ }
},
- knight: {
- assetPrefix: 'knight',
- scale: 3.0,
- baseHealth: 300,
- baseSpeed: 2,
- animationSpeed: 22,
- vibration: 100,
- damageTextSize: 140,
- damageTextColor: 0xFFD700,
- impactScale: 1.8,
- deathRotation: Math.PI * 0.8,
- scoreReward: 20,
- startThreshold: {
- FACIL: 40,
- NORMAL: 30,
- DIFICIL: 20
- },
- coinReward: 1,
- damage: 40,
- spawnInterval: {
- FACIL: 420,
- NORMAL: 300,
- DIFICIL: 240
- },
- arrayName: 'knights',
- maxOnScreen: 3,
- pathPreference: 'any',
- eliteChance: 0.10,
- armor: 50,
- resistances: ['physical'],
- specialAbilities: ['block', 'counter']
- },
- miniBoss: {
- assetPrefix: 'knight',
- scale: 5.0,
- baseHealth: 3000,
- baseSpeed: 4,
- animationSpeed: 12,
- vibration: [100, 50, 100],
- damageTextSize: 160,
- damageTextColor: 0xFF0000,
- impactScale: 2.5,
- deathRotation: Math.PI * 2,
- scoreReward: 100,
- tint: 0x8B0000,
- startThreshold: 80,
- endThreshold: 85,
- spawnChance: 0.02,
- coinReward: 5,
- damage: 75,
- arrayName: 'miniBosses',
- maxOnScreen: 1,
- pathPreference: 'center',
- phases: [{
- healthThreshold: 0.66,
- speedBoost: 1.5,
- abilities: ['rage']
- }, {
- healthThreshold: 0.33,
- speedBoost: 2.0,
- abilities: ['rage', 'summon']
- }],
- immunities: ['freeze', 'slow'],
- specialAbilities: ['regeneration', 'aoe_attack']
- }
- },
- // Base enemy template for inheritance
- createEnemyConfig: function createEnemyConfig(type, overrides) {
- var base = this.enemies[type];
- if (!base) {
- console.error('Enemy type not found:', type);
- return this.enemies.skeleton; // fallback to skeleton
- }
- var config = {};
- // Deep copy base configuration
- for (var key in base) {
- if (_typeof(base[key]) === 'object' && base[key] !== null && !Array.isArray(base[key])) {
- config[key] = {};
- for (var subKey in base[key]) {
- config[key][subKey] = base[key][subKey];
- }
- } else {
- config[key] = base[key];
+ // Entity configuration factory
+ createEnemyConfig: function createEnemyConfig(type, overrides) {
+ var base = this.Enemy[type];
+ if (!base) {
+ console.error('Enemy type not found:', type);
+ return this.Enemy.skeleton; // fallback to skeleton
}
- }
- // Apply overrides
- if (overrides) {
- for (var key in overrides) {
- if (_typeof(overrides[key]) === 'object' && overrides[key] !== null && !Array.isArray(overrides[key])) {
- if (!config[key]) config[key] = {};
- for (var subKey in overrides[key]) {
- config[key][subKey] = overrides[key][subKey];
+ var config = {};
+ // Deep copy base configuration
+ for (var key in base) {
+ if (_typeof(base[key]) === 'object' && base[key] !== null && !Array.isArray(base[key])) {
+ config[key] = {};
+ for (var subKey in base[key]) {
+ config[key][subKey] = base[key][subKey];
}
} else {
- config[key] = overrides[key];
+ config[key] = base[key];
}
}
+ // Apply overrides
+ if (overrides) {
+ for (var key in overrides) {
+ if (_typeof(overrides[key]) === 'object' && overrides[key] !== null && !Array.isArray(overrides[key])) {
+ if (!config[key]) config[key] = {};
+ for (var subKey in overrides[key]) {
+ config[key][subKey] = overrides[key][subKey];
+ }
+ } else {
+ config[key] = overrides[key];
+ }
+ }
+ }
+ return config;
}
- return config;
},
- // Upgrade system removed - using spell deck system instead
- // Path configurations
- paths: {
- count: 5,
- angles: [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6],
- spawnPositions: [{
- x: 2048 / 2,
- y: -100
+ // Configuration management
+ Config: {
+ // Path configurations
+ paths: {
+ count: 5,
+ angles: [-Math.PI / 2, -Math.PI / 3, -2 * Math.PI / 3, Math.PI / 6, 5 * Math.PI / 6],
+ spawnPositions: [{
+ x: 2048 / 2,
+ y: -100
+ },
+ // Center path
+ {
+ x: 2048 + 50,
+ y: -50
+ },
+ // Top right
+ {
+ x: -50,
+ y: -50
+ },
+ // Top left
+ {
+ x: -100,
+ y: 2732 / 2 + 400
+ },
+ // Left edge
+ {
+ x: 2048 + 100,
+ y: 2732 / 2 + 400
+ } // Right edge
+ ]
},
- // Center path
- {
- x: 2048 + 50,
- y: -50
+ // Gameplay configurations
+ gameplay: {
+ maxEnemiesOnScreen: 15,
+ pathCooldown: 300,
+ upgradeMenuTriggers: [12, 35, 50, 70],
+ baseUpgradeCost: 5,
+ upgradeCostIncrease: 5,
+ attackCooldown: 30,
+ projectileSpeed: 50,
+ wizardAnimationSpeed: 18,
+ segmentSize: 120,
+ difficultyScaling: {
+ FACIL: {
+ healthMult: 0.8,
+ speedMult: 0.9,
+ bonusCoins: true,
+ healingChance: 0.15
+ },
+ NORMAL: {
+ healthMult: 1.0,
+ speedMult: 1.0,
+ standardRewards: true
+ },
+ DIFICIL: {
+ healthMult: 1.2,
+ speedMult: 1.1,
+ eliteEnemies: true,
+ reducedCoins: true
+ }
+ }
},
- // Top right
- {
- x: -50,
- y: -50
- },
- // Top left
- {
- x: -100,
- y: 2732 / 2 + 400
- },
- // Left edge
- {
- x: 2048 + 100,
- y: 2732 / 2 + 400
- } // Right edge
- ]
- },
- // UI configurations
- ui: {
- healthBar: {
- x: 120,
- y: 20,
- scaleX: 1.0,
- scaleY: 1.0
- },
- healthBarBg: {
- x: 120,
- y: 20,
- scaleX: 1.0,
- scaleY: 1.0
- },
- coinText: {
- x: 120,
- y: 90,
- size: 60,
- fill: 0xFFD700,
- font: "monospace"
- },
- killText: {
- x: 120,
- y: 150,
- size: 60,
- fill: 0xFF6B6B,
- font: "monospace"
- },
- healthText: {
- x: 120,
- y: 50,
- size: 50,
- fill: 0xFFFFFF,
- font: "monospace"
- },
- tapText: {
- size: 100,
- yOffset: -200,
- fill: 0xFF6B6B,
- font: "monospace"
- },
- manaBar: {
- x: -300,
- y: 20,
- scaleX: 2,
- scaleY: 0.5
- },
- manaBarBg: {
- x: -300,
- y: 20,
- scaleX: 2,
- scaleY: 0.5
- },
- manaText: {
- x: -300,
- y: 50,
- size: 40,
- fill: 0x4169E1,
- font: "monospace"
- },
- spellSlots: {
- startX: -200,
- spacing: 100,
- y: -100,
- scaleX: 1.5,
- scaleY: 1.5,
- count: 5
- },
- miniBossHealthBar: {
- x: 2048 / 2,
- y: 200,
- bgScaleX: 1.0,
- bgScaleY: 1.0,
- fgScaleX: 1.0,
- fgScaleY: 1.0,
- textSize: 40,
- textY: 150
+ // Projectile configurations
+ projectiles: {
+ projectile: {
+ assetId: 'projectile',
+ glowAsset: 'projectileGlow',
+ scale: 1.5,
+ speed: 50,
+ damage: 100,
+ tint: 0x44aaff,
+ glowTint: 0x44aaff,
+ hasTrail: false,
+ hasRotation: true
+ },
+ energyBeam: {
+ assetId: 'projectileGlow',
+ glowAsset: 'projectileGlow',
+ scale: 1.0,
+ speed: 60,
+ damage: 100,
+ tint: 0x00ffff,
+ glowTint: 0x00ffff,
+ hasTrail: false,
+ hasRotation: true
+ },
+ fireBall: {
+ assetId: 'projectileGlow',
+ glowAsset: 'projectileGlow',
+ scale: 1.5,
+ speed: 40,
+ damage: 150,
+ tint: 0xFF4500,
+ glowTint: 0xFF6600,
+ hasTrail: true,
+ hasRotation: true,
+ hasFlicker: true
+ }
}
},
- // Gameplay configurations
- gameplay: {
- maxEnemiesOnScreen: 15,
- pathCooldown: 300,
- upgradeMenuTriggers: [12, 35, 50, 70],
- baseUpgradeCost: 5,
- upgradeCostIncrease: 5,
- attackCooldown: 30,
- projectileSpeed: 50,
- wizardAnimationSpeed: 18,
- segmentSize: 120,
- difficultyScaling: {
- FACIL: {
- healthMult: 0.8,
- speedMult: 0.9,
- bonusCoins: true,
- healingChance: 0.15
+ // UI management
+ UI: {
+ positions: {
+ healthBar: {
+ x: 120,
+ y: 20,
+ scaleX: 1.0,
+ scaleY: 1.0
},
- NORMAL: {
- healthMult: 1.0,
- speedMult: 1.0,
- standardRewards: true
+ healthBarBg: {
+ x: 120,
+ y: 20,
+ scaleX: 1.0,
+ scaleY: 1.0
},
- DIFICIL: {
- healthMult: 1.2,
- speedMult: 1.1,
- eliteEnemies: true,
- reducedCoins: true
+ coinText: {
+ x: 120,
+ y: 90,
+ size: 60,
+ fill: 0xFFD700,
+ font: "monospace"
+ },
+ killText: {
+ x: 120,
+ y: 150,
+ size: 60,
+ fill: 0xFF6B6B,
+ font: "monospace"
+ },
+ healthText: {
+ x: 120,
+ y: 50,
+ size: 50,
+ fill: 0xFFFFFF,
+ font: "monospace"
+ },
+ tapText: {
+ size: 100,
+ yOffset: -200,
+ fill: 0xFF6B6B,
+ font: "monospace"
+ },
+ manaBar: {
+ x: -300,
+ y: 20,
+ scaleX: 2,
+ scaleY: 0.5
+ },
+ manaBarBg: {
+ x: -300,
+ y: 20,
+ scaleX: 2,
+ scaleY: 0.5
+ },
+ manaText: {
+ x: -300,
+ y: 50,
+ size: 40,
+ fill: 0x4169E1,
+ font: "monospace"
+ },
+ spellSlots: {
+ startX: -200,
+ spacing: 100,
+ y: -100,
+ scaleX: 1.5,
+ scaleY: 1.5,
+ count: 5
+ },
+ miniBossHealthBar: {
+ x: 2048 / 2,
+ y: 200,
+ bgScaleX: 1.0,
+ bgScaleY: 1.0,
+ fgScaleX: 1.0,
+ fgScaleY: 1.0,
+ textSize: 40,
+ textY: 150
}
}
- },
- // Projectile configurations
- projectiles: {
- projectile: {
- assetId: 'projectile',
- glowAsset: 'projectileGlow',
- scale: 1.5,
- speed: 50,
- damage: 100,
- tint: 0x44aaff,
- glowTint: 0x44aaff,
- hasTrail: false,
- hasRotation: true
- },
- energyBeam: {
- assetId: 'projectileGlow',
- glowAsset: 'projectileGlow',
- scale: 1.0,
- speed: 60,
- damage: 100,
- tint: 0x00ffff,
- glowTint: 0x00ffff,
- hasTrail: false,
- hasRotation: true
- },
- fireBall: {
- assetId: 'projectileGlow',
- glowAsset: 'projectileGlow',
- scale: 1.5,
- speed: 40,
- damage: 150,
- tint: 0xFF4500,
- glowTint: 0xFF6600,
- hasTrail: true,
- hasRotation: true,
- hasFlicker: true
- }
}
};
+// Backward compatibility aliases
+var GAME_CONFIG = {
+ events: GAME.Core.Events,
+ state: GAME.Core.State,
+ enemies: GAME.Entities.Enemy,
+ createEnemyConfig: GAME.Entities.createEnemyConfig,
+ paths: GAME.Config.paths,
+ ui: GAME.UI.positions,
+ gameplay: GAME.Config.gameplay,
+ projectiles: GAME.Config.projectiles
+};
// Game state variables
var gameStarted = false;
var gameMenu;
// Upgrade menu variables removed