User prompt
make secret towers if you choose to not restart at the end of the game that are EX versions of each tower and make the towers the same sprites but tinted red ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the player bullet's fire faster and make the bullets do .5 dmage
User prompt
make the boss bullets take 2 hits to destroy with your bullets
User prompt
remove the attack interface
User prompt
not the attack interface! the thing i want to spawn in the same row as the heart is Sword_event
User prompt
make the attack button for the boss fights spawns on the row where player spawns
User prompt
make the bosses take damage from a thing that randomly appears during the boss battle that has a picture of a sword and when your heart touches it it does damage to the boss.
User prompt
make a hit sound when you hit the enemy with a bullet
User prompt
make a battle theme
User prompt
make the username of the person playing show on the bottom instead of the bullet
User prompt
make each character have different HP and each attack the enemy targets one of the heroes and make the bullets do .2 Damage
User prompt
remove the attack option and make the magic have a healing spell that gives you + 5 HP
User prompt
make the bullets do .5 damage every time the enemy collides, and make the enemies have more HP
User prompt
Please fix the bug: 'Uncaught ReferenceError: shopActive is not defined' in or related to this line: 'if (shopActive && shopMenu) {' Line Number: 1848
User prompt
Please fix the bug: 'Uncaught ReferenceError: playerTurnActive is not defined' in or related to this line: 'if (playerTurnActive && playerTurnMenu) {' Line Number: 1803
User prompt
make a few versions of the mimic that have a random chance to have a different sprite
User prompt
Please fix the bug: 'Uncaught ReferenceError: playerTurnActive is not defined' in or related to this line: 'if (playerTurnActive && playerTurnMenu) {' Line Number: 1797
User prompt
make images for each enemy and make it to where you choose what you do on your turn like, attack, items (So you'd have to add a shop) and magic
User prompt
can you make the heart shoot yellow bullets when dragged to destroy bullets the enemy shoots
User prompt
make a white box around the place where you can move the heart
User prompt
make it take 3 seconds before the enemy attacks ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
and let you fight the enemy first
User prompt
make a few minor enemies fights before the boss fight
User prompt
Please fix the bug: 'Uncaught TypeError: storage.set is not a function' in or related to this line: 'storage.set('selectedParty', selectedParty.slice());' Line Number: 496 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
make a knight, but keep it so you can only have 3 teammates but it shows the stats of each character on the bottom and make a reset game data button below the stats incase you want to play the game again ↪💡 Consider importing and using the following plugins: @upit/storage.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AmberBoss = Container.expand(function () {
var self = Container.call(this);
self.hp = 70;
self.maxHP = 70;
self.exp = 70;
self.sprite = self.attachAsset('boss_amber', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
// Boss Classes
var AmethystBoss = Container.expand(function () {
var self = Container.call(this);
self.hp = 50;
self.maxHP = 50;
self.exp = 50;
self.sprite = self.attachAsset('boss_amethyst', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var BatEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_bat', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var CrimsonBoss = Container.expand(function () {
var self = Container.call(this);
self.hp = 80;
self.maxHP = 80;
self.exp = 80;
self.sprite = self.attachAsset('boss_crimson', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var EmeraldBoss = Container.expand(function () {
var self = Container.call(this);
self.hp = 60;
self.maxHP = 60;
self.exp = 60;
self.sprite = self.attachAsset('boss_emerald', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('enemy_bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.radius = 30;
self.active = true;
self.hp = 1; // Default, will be set to 2 for boss fight bullets
self.init = function (x, y, vx, vy, isBossFight) {
self.x = x;
self.y = y;
self.vx = vx;
self.vy = vy;
self.hp = isBossFight ? 2 : 1;
};
self.update = function () {
if (!self.active) return;
self.x += self.vx;
self.y += self.vy;
// Offscreen check
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.active = false;
self.destroy();
}
};
return self;
});
// Minor Enemy Classes
var GoblinEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_goblin', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
// Heart (player dodge object)
var Heart = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 40;
self.invincible = false;
self.invincibleTicks = 0;
self.setInvincible = function (ticks) {
self.invincible = true;
self.invincibleTicks = ticks;
self.sprite.alpha = 0.5;
};
self.update = function () {
if (self.invincible) {
self.invincibleTicks--;
if (self.invincibleTicks <= 0) {
self.invincible = false;
self.sprite.alpha = 1;
}
}
};
return self;
});
// HeartBullet (player's yellow bullet)
var HeartBullet = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xb8b031,
// yellow
width: 60,
height: 60,
shape: 'box'
});
self.radius = 30;
self.vx = 0;
self.vy = -18;
self.active = true;
self.init = function (x, y) {
self.x = x;
self.y = y;
};
self.update = function () {
if (!self.active) return;
self.y += self.vy;
// Offscreen check
if (self.y < -100) {
self.active = false;
self.destroy();
}
};
return self;
});
var ImpEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_imp', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var MimicEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
// Randomly select a mimic sprite variant
var mimicVariants = ['enemy_mimic', 'enemy_mimic2', 'enemy_mimic3'];
var idx = Math.floor(Math.random() * mimicVariants.length);
self.sprite = self.attachAsset(mimicVariants[idx], {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var ObsidianBoss = Container.expand(function () {
var self = Container.call(this);
self.hp = 100;
self.maxHP = 100;
self.exp = 100;
self.sprite = self.attachAsset('boss_obsidian', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
// Orb Icon
var OrbIcon = Container.expand(function () {
var self = Container.call(this);
self.orbId = 1;
self.collected = false;
self.icon = null;
self.setOrb = function (orbId, collected) {
self.orbId = orbId;
self.collected = collected;
if (self.icon) self.icon.destroy();
var assetId = 'orb' + orbId;
self.icon = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.icon.alpha = collected ? 1 : 0.3;
};
return self;
});
// Party Member Icon
var PartyIcon = Container.expand(function () {
var self = Container.call(this);
self.type = null;
self.selected = false;
self.icon = null;
self.setType = function (type) {
self.type = type;
if (self.icon) self.icon.destroy();
var assetId = '';
if (type === 'white_mage') assetId = 'icon_white_mage';
if (type === 'thief') assetId = 'icon_thief';
if (type === 'martial') assetId = 'icon_martial';
if (type === 'knight') assetId = 'icon_knight';
self.icon = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setSelected = function (selected) {
self.selected = selected;
if (self.icon) {
self.icon.alpha = selected ? 1 : 0.6;
self.icon.scaleX = self.icon.scaleY = selected ? 1.15 : 1;
}
};
return self;
});
var RatEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_rat', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var SlimeEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_slime', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
var SpriteEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_sprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
// SwordEvent: sword that appears randomly during boss fight
var SwordEvent = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('sword_event', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 60;
self.active = true;
self.vanishTicks = 120; // lasts for 2 seconds
self.update = function () {
if (!self.active) return;
self.vanishTicks--;
if (self.vanishTicks <= 0) {
self.active = false;
self.destroy();
}
};
return self;
});
// Tower Icon
var TowerIcon = Container.expand(function () {
var self = Container.call(this);
self.towerId = 1;
self.locked = false;
self.icon = null;
self.setTower = function (towerId, locked) {
self.towerId = towerId;
self.locked = locked;
if (self.icon) self.icon.destroy();
var assetId = 'tower' + towerId;
self.icon = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 1
});
// Tint red if EX mode is active
if (typeof game !== "undefined" && game._exModeActive) {
// Use tween for instant tint
tween(self.icon, {
tint: 0xFF3333
}, {
duration: 0
});
}
self.icon.alpha = locked ? 0.4 : 1;
};
return self;
});
var WispEnemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 10;
self.maxHP = 10;
self.exp = 3;
self.sprite = self.attachAsset('enemy_wisp', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222233
});
/****
* Game Code
****/
// sword image for boss event
// --- PLAYER TURN MENU REMOVED ---
// (player turn menu and related variables removed)
var shopMenu = null; // Ensure shopMenu is defined globally
var shopActive = false; // Ensure shopActive is defined globally
// Enemy images
// --- STORAGE PLUGIN ---
// Music
// Sound effects
// Orb icons
// Tower icons
// Enemy bullet
// Heart (player dodge object)
// Party member icons
// Game state
var STATE_PARTY_SELECT = 0;
var STATE_TOWER_SELECT = 1;
var STATE_BATTLE = 2;
var STATE_BOSS_DEFEAT = 3;
var STATE_GAME_COMPLETE = 4;
var gameState = STATE_PARTY_SELECT;
// Party selection
var partyMembers = ['white_mage', 'thief', 'martial', 'knight'];
// Load persistent party and orbs if available
var selectedParty = [];
var collectedOrbs = [false, false, false, false];
(function () {
var savedParty = storage.selectedParty;
var savedOrbs = storage.collectedOrbs;
if (savedParty && Array.isArray(savedParty) && savedParty.length === 3) {
selectedParty = savedParty.slice();
}
if (savedOrbs && Array.isArray(savedOrbs) && savedOrbs.length === 4) {
collectedOrbs = savedOrbs.slice();
}
})();
var partyIcons = [];
var partySelectText = null;
// Tower selection
var towers = [{
id: 1,
name: 'Amethyst Tower',
orb: 1
}, {
id: 2,
name: 'Emerald Tower',
orb: 2
}, {
id: 3,
name: 'Amber Tower',
orb: 3
}, {
id: 4,
name: 'Crimson Tower',
orb: 4
}, {
id: 5,
name: 'Obsidian Tower',
orb: 0
}];
var towerIcons = [];
var orbIcons = [];
var towerSelectText = null;
// Battle
var heart = null;
var heartStartX = 0;
var heartStartY = 0;
var heartDragging = false;
var heartDragOffsetX = 0;
var heartDragOffsetY = 0;
var battleBullets = [];
var battleTimer = 0;
var battlePhase = 0;
var battleBoss = null;
var battleBossId = 0;
var battleBossHP = 0;
var battleBossMaxHP = 0;
var battleBossText = null;
var battleHPText = null;
var battleOrbsText = null;
var battleActive = false;
var battleWin = false;
var battleLose = false;
var battleEndTimeout = null;
// GUI
var guiParty = [];
var guiOrbs = [];
var guiHP = null;
// --- CHARACTER STATS ---
var characterStats = {
white_mage: {
name: "White Mage",
hp: 18,
atk: 6,
def: 8,
desc: "Heals and supports the party."
},
thief: {
name: "Thief",
hp: 14,
atk: 10,
def: 5,
desc: "Fast and nimble, high critical chance."
},
martial: {
name: "Martial Artist",
hp: 20,
atk: 8,
def: 7,
desc: "Balanced fighter with strong attacks."
},
knight: {
name: "Knight",
hp: 24,
atk: 7,
def: 12,
desc: "High defense, protects the party."
}
};
// Track current HP for each party member during battle
var partyCurrentHP = [];
var statTextObjs = [];
var resetButton = null;
function showCharacterStatsAndReset() {
// Remove old stats if present
for (var i = 0; i < statTextObjs.length; i++) statTextObjs[i].destroy();
statTextObjs = [];
if (resetButton) {
resetButton.destroy();
resetButton = null;
}
var statsY = 2500;
var statsX = 200;
var spacing = 480;
for (var i = 0; i < partyMembers.length; i++) {
var type = partyMembers[i];
var stats = characterStats[type];
var txt = new Text2(stats.name + " HP:" + stats.hp + " ATK:" + stats.atk + " DEF:" + stats.def, {
size: 60,
fill: "#fff"
});
txt.anchor.set(0, 0);
txt.x = statsX + i * spacing;
txt.y = statsY;
LK.gui.bottom.addChild(txt);
statTextObjs.push(txt);
}
// Add reset button below stats
resetButton = new Text2("Reset Game Data", {
size: 70,
fill: 0xFF3366
});
resetButton.anchor.set(0.5, 0);
resetButton.x = 2048 / 2;
resetButton.y = statsY + 120;
resetButton.interactive = true;
resetButton.buttonMode = true;
resetButton._isResetButton = true;
LK.gui.bottom.addChild(resetButton);
}
function clearCharacterStatsAndReset() {
for (var i = 0; i < statTextObjs.length; i++) statTextObjs[i].destroy();
statTextObjs = [];
if (resetButton) {
resetButton.destroy();
resetButton = null;
}
}
// Utility
function resetGame() {
// Reset all state
gameState = STATE_PARTY_SELECT;
selectedParty = [];
collectedOrbs = [false, false, false, false];
for (var i = 0; i < partyIcons.length; i++) partyIcons[i].destroy();
partyIcons = [];
for (var i = 0; i < towerIcons.length; i++) towerIcons[i].destroy();
towerIcons = [];
for (var i = 0; i < orbIcons.length; i++) orbIcons[i].destroy();
orbIcons = [];
if (partySelectText) {
partySelectText.destroy();
partySelectText = null;
}
if (towerSelectText) {
towerSelectText.destroy();
towerSelectText = null;
}
if (heart) {
heart.destroy();
heart = null;
}
for (var i = 0; i < battleBullets.length; i++) battleBullets[i].destroy();
battleBullets = [];
if (battleBossText) {
battleBossText.destroy();
battleBossText = null;
}
if (battleHPText) {
battleHPText.destroy();
battleHPText = null;
}
if (battleOrbsText) {
battleOrbsText.destroy();
battleOrbsText = null;
}
if (battleEndTimeout) {
LK.clearTimeout(battleEndTimeout);
battleEndTimeout = null;
}
for (var i = 0; i < guiParty.length; i++) guiParty[i].destroy();
guiParty = [];
for (var i = 0; i < guiOrbs.length; i++) guiOrbs[i].destroy();
guiOrbs = [];
if (guiHP) {
guiHP.destroy();
guiHP = null;
}
LK.setScore(0);
// Clear persistent stats on full reset
delete storage.selectedParty;
delete storage.collectedOrbs;
startPartySelect();
}
// --- PARTY SELECT ---
function startPartySelect() {
clearCharacterStatsAndReset();
showCharacterStatsAndReset();
gameState = STATE_PARTY_SELECT;
selectedParty = [];
// Show party selection text
partySelectText = new Text2('Choose your party (tap to select 3):', {
size: 90,
fill: '#fff'
});
partySelectText.anchor.set(0.5, 0);
partySelectText.x = 2048 / 2;
partySelectText.y = 180;
game.addChild(partySelectText);
// Show icons for each member
var spacing = 400;
var startX = 2048 / 2 - spacing;
for (var i = 0; i < partyMembers.length; i++) {
var icon = new PartyIcon();
icon.setType(partyMembers[i]);
icon.x = startX + i * spacing;
icon.y = 600;
icon.setSelected(false);
icon.memberId = partyMembers[i];
icon.index = i;
partyIcons.push(icon);
game.addChild(icon);
}
}
// Handle party icon selection
function handlePartySelect(x, y, obj) {
if (gameState !== STATE_PARTY_SELECT) return;
for (var i = 0; i < partyIcons.length; i++) {
var icon = partyIcons[i];
var dx = x - icon.x;
var dy = y - icon.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 80) {
// Toggle selection
var idx = selectedParty.indexOf(icon.memberId);
if (idx === -1 && selectedParty.length < 3) {
selectedParty.push(icon.memberId);
icon.setSelected(true);
} else if (idx !== -1) {
selectedParty.splice(idx, 1);
icon.setSelected(false);
}
// If 3 selected, proceed
if (selectedParty.length === 3) {
// Save party selection persistently
storage.selectedParty = selectedParty.slice();
LK.setTimeout(function () {
for (var j = 0; j < partyIcons.length; j++) partyIcons[j].destroy();
partyIcons = [];
if (partySelectText) {
partySelectText.destroy();
partySelectText = null;
}
startTowerSelect();
}, 400);
}
break;
}
}
}
// --- TOWER SELECT ---
function startTowerSelect() {
clearCharacterStatsAndReset();
showCharacterStatsAndReset();
gameState = STATE_TOWER_SELECT;
// Show tower select text
towerSelectText = new Text2('Choose a tower to conquer:', {
size: 90,
fill: '#fff'
});
towerSelectText.anchor.set(0.5, 0);
towerSelectText.x = 2048 / 2;
towerSelectText.y = 180;
game.addChild(towerSelectText);
// Show towers
var spacing = 350;
var startX = 2048 / 2 - spacing * 2 + spacing / 2;
for (var i = 0; i < towers.length; i++) {
var locked = false;
if (i === 4) {
// Fifth tower locked until all orbs
locked = !(collectedOrbs[0] && collectedOrbs[1] && collectedOrbs[2] && collectedOrbs[3]);
}
var icon = new TowerIcon();
icon.setTower(towers[i].id, locked);
icon.x = startX + i * spacing;
icon.y = 700;
icon.towerIndex = i;
towerIcons.push(icon);
game.addChild(icon);
// Add EX label if in EX mode
if (typeof game !== "undefined" && game._exModeActive) {
var exLabel = new Text2("EX", {
size: 60,
fill: 0xFF3333
});
exLabel.anchor.set(0.5, 0.5);
exLabel.x = icon.x;
exLabel.y = icon.y - 180;
game.addChild(exLabel);
icon._exLabel = exLabel;
}
}
// Show orbs collected
for (var i = 0; i < 4; i++) {
var orb = new OrbIcon();
orb.setOrb(i + 1, collectedOrbs[i]);
orb.x = 2048 / 2 - 200 + i * 130;
orb.y = 1200;
orbIcons.push(orb);
game.addChild(orb);
}
}
// Handle tower icon selection
function handleTowerSelect(x, y, obj) {
if (gameState !== STATE_TOWER_SELECT) return;
for (var i = 0; i < towerIcons.length; i++) {
var icon = towerIcons[i];
var dx = x - icon.x;
var dy = y - (icon.y - 150);
if (dx > -90 && dx < 90 && dy > 0 && dy < 300) {
if (icon.locked) {
// Do nothing
return;
}
// Start battle for this tower
var towerIdx = icon.towerId - 1;
for (var j = 0; j < towerIcons.length; j++) towerIcons[j].destroy();
towerIcons = [];
for (var j = 0; j < orbIcons.length; j++) orbIcons[j].destroy();
orbIcons = [];
if (towerSelectText) {
towerSelectText.destroy();
towerSelectText = null;
}
startBattle(towerIdx);
break;
}
}
}
// --- BATTLE ---
function startBattle(towerIdx) {
clearCharacterStatsAndReset();
gameState = STATE_BATTLE;
battleActive = true;
battleWin = false;
battleLose = false;
battlePhase = 0;
battleTimer = 0;
battleBossId = towerIdx + 1;
if (typeof game !== "undefined" && game._exModeActive) {
// EX mode: more HP
battleBossHP = (40 + towerIdx * 20) * 2.2;
} else {
battleBossHP = 40 + towerIdx * 20;
}
battleBossMaxHP = battleBossHP;
battleBullets = [];
// Initialize each party member's HP
partyCurrentHP = [];
for (var i = 0; i < selectedParty.length; i++) {
var type = selectedParty[i];
partyCurrentHP[i] = characterStats[type].hp;
}
// --- MINOR ENEMY FIGHTS SETUP ---
var minorEnemies = [];
var minorEnemyCount = 2 + towerIdx; // 2,3,4,5,6 for each tower
var minorEnemyHP = 16 + towerIdx * 6;
var minorEnemyIdx = 0;
var inMinorFight = true;
var minorEnemyObj = null;
var minorEnemyText = null;
var minorEnemyHPText = null;
var minorEnemyPatternTimer = 0;
var minorEnemyPatternId = 0;
var minorEnemyNames = ["Goblin", "Slime", "Bat", "Imp", "Wisp", "Rat", "Mimic", "Sprite"];
var minorEnemyClasses = [GoblinEnemy, SlimeEnemy, BatEnemy, ImpEnemy, WispEnemy, RatEnemy, MimicEnemy, SpriteEnemy];
var minorEnemyName = "";
var minorEnemyClass = GoblinEnemy;
// Start with minor enemy fight before boss
inMinorFight = true;
minorEnemyIdx = 0;
// Draw white box for heart movement area
if (typeof heartBox !== "undefined" && heartBox) {
heartBox.destroy();
heartBox = null;
}
var heartBox = new Container();
var boxMinX = 2048 / 2 - 350;
var boxMaxX = 2048 / 2 + 350;
var boxMinY = 1200;
var boxMaxY = 2200;
var boxW = boxMaxX - boxMinX;
var boxH = boxMaxY - boxMinY;
var borderW = 8;
var boxColor = 0xffffff;
// Top border
var topLine = LK.getAsset('bullet', {
width: boxW,
height: borderW,
color: boxColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
topLine.x = boxMinX;
topLine.y = boxMinY;
heartBox.addChild(topLine);
// Bottom border
var bottomLine = LK.getAsset('bullet', {
width: boxW,
height: borderW,
color: boxColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
bottomLine.x = boxMinX;
bottomLine.y = boxMaxY - borderW;
heartBox.addChild(bottomLine);
// Left border
var leftLine = LK.getAsset('bullet', {
width: borderW,
height: boxH,
color: boxColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
leftLine.x = boxMinX;
leftLine.y = boxMinY;
heartBox.addChild(leftLine);
// Right border
var rightLine = LK.getAsset('bullet', {
width: borderW,
height: boxH,
color: boxColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
rightLine.x = boxMaxX - borderW;
rightLine.y = boxMinY;
heartBox.addChild(rightLine);
game.addChild(heartBox);
// Show heart
heart = new Heart();
heart.x = 2048 / 2;
heart.y = 1800;
heartStartX = heart.x;
heartStartY = heart.y;
game.addChild(heart);
// Show party in GUI
for (var i = 0; i < selectedParty.length; i++) {
var icon = new PartyIcon();
icon.setType(selectedParty[i]);
icon.x = 200 + i * 150;
icon.y = 100;
icon.setSelected(true);
guiParty.push(icon);
LK.gui.top.addChild(icon);
}
// Show orbs in GUI
for (var i = 0; i < 4; i++) {
var orb = new OrbIcon();
orb.setOrb(i + 1, collectedOrbs[i]);
orb.x = 2048 - 200 - (3 - i) * 120;
orb.y = 100;
guiOrbs.push(orb);
LK.gui.top.addChild(orb);
}
// Show HP in GUI
// Show each party member's HP
if (guiHP) {
guiHP.destroy();
guiHP = null;
}
guiHP = [];
for (var i = 0; i < selectedParty.length; i++) {
var hpText = new Text2('❤ ' + partyCurrentHP[i], {
size: 80,
fill: '#ff3366'
});
hpText.anchor.set(0, 0);
hpText.x = 120 + i * 220;
hpText.y = 220;
LK.gui.top.addChild(hpText);
guiHP.push(hpText);
}
// Show orbs
battleOrbsText = new Text2('Orbs: ' + getOrbsCount() + '/4', {
size: 60,
fill: '#fff'
});
battleOrbsText.anchor.set(0.5, 0);
battleOrbsText.x = 2048 / 2;
battleOrbsText.y = 450;
game.addChild(battleOrbsText);
// --- ENEMY ATTACK DELAY STATE ---
var enemyAttackDelayTicks = 180; // 3 seconds at 60fps
var enemyAttackDelayActive = true;
// --- PLAYER TURN MENU REMOVED ---
// (player turn menu, shop, and magic menu functions removed)
var shopMenu = null;
var shopActive = false;
// --- MINOR ENEMY FIGHT LOGIC ---
function startMinorEnemyFight(idx) {
// Clean up previous
if (minorEnemyObj) {
minorEnemyObj.destroy();
minorEnemyObj = null;
}
if (minorEnemyText) {
minorEnemyText.destroy();
minorEnemyText = null;
}
if (minorEnemyHPText) {
minorEnemyHPText.destroy();
minorEnemyHPText = null;
}
minorEnemyPatternTimer = 0;
minorEnemyPatternId = idx % 4;
minorEnemyName = minorEnemyNames[idx % minorEnemyNames.length];
minorEnemyClass = minorEnemyClasses[idx % minorEnemyClasses.length];
// Create enemy
minorEnemyObj = new minorEnemyClass();
minorEnemyObj.hp = minorEnemyHP;
minorEnemyObj.maxHP = minorEnemyHP;
minorEnemyObj.x = 2048 / 2;
minorEnemyObj.y = 700;
game.addChild(minorEnemyObj);
// Name
minorEnemyText = new Text2(minorEnemyName, {
size: 90,
fill: '#fff'
});
minorEnemyText.anchor.set(0.5, 0);
minorEnemyText.x = 2048 / 2;
minorEnemyText.y = 200;
game.addChild(minorEnemyText);
// HP
minorEnemyHPText = new Text2('HP: ' + minorEnemyObj.hp, {
size: 70,
fill: '#fff'
});
minorEnemyHPText.anchor.set(0.5, 0);
minorEnemyHPText.x = 2048 / 2;
minorEnemyHPText.y = 320;
game.addChild(minorEnemyHPText);
// Reset enemy attack delay for each new minor enemy
enemyAttackDelayTicks = 180;
enemyAttackDelayActive = true;
}
function cleanupMinorEnemyFight() {
if (minorEnemyObj) {
minorEnemyObj.destroy();
minorEnemyObj = null;
}
if (minorEnemyText) {
minorEnemyText.destroy();
minorEnemyText = null;
}
if (minorEnemyHPText) {
minorEnemyHPText.destroy();
minorEnemyHPText = null;
}
for (var i = battleBullets.length - 1; i >= 0; i--) {
battleBullets[i].destroy();
battleBullets.splice(i, 1);
}
LK.setScore(0);
}
function updateMinorEnemyFight() {
if (!minorEnemyObj) return;
// Update heart
if (heart) heart.update();
// Update bullets
for (var i = battleBullets.length - 1; i >= 0; i--) {
var b = battleBullets[i];
b.update();
if (!b.active) {
battleBullets.splice(i, 1);
continue;
}
// Heart bullet destroys enemy bullet
if (b instanceof HeartBullet) {
// Check collision with enemy bullets
for (var j = battleBullets.length - 1; j >= 0; j--) {
var eb = battleBullets[j];
if (eb === b) continue;
if (eb instanceof EnemyBullet && eb.active && b.active && circleIntersect(b.x, b.y, b.radius, eb.x, eb.y, eb.radius)) {
eb.active = false;
eb.destroy();
b.active = false;
b.destroy();
battleBullets.splice(j, 1);
break;
}
}
// Heart bullet hits minor enemy
if (minorEnemyObj && b.active && circleIntersect(b.x, b.y, b.radius, minorEnemyObj.x, minorEnemyObj.y, 60)) {
minorEnemyObj.hp -= 0.5;
if (minorEnemyObj.hp < 0) minorEnemyObj.hp = 0;
if (minorEnemyHPText) minorEnemyHPText.setText('HP: ' + Math.max(0, Math.round(minorEnemyObj.hp * 10) / 10));
LK.getSound('hit').play();
b.active = false;
b.destroy();
}
if (!b.active) {
battleBullets.splice(i, 1);
continue;
}
}
// Collision with heart
if (!heart.invincible && b instanceof EnemyBullet && circleIntersect(heart.x, heart.y, heart.radius, b.x, b.y, b.radius)) {
heart.setInvincible(40);
LK.effects.flashObject(heart, 0xff0000, 400);
LK.getSound('hit').play();
// Randomly select a party member to take damage
if (partyCurrentHP.length > 0) {
var targetIdx = Math.floor(Math.random() * partyCurrentHP.length);
partyCurrentHP[targetIdx] -= 1;
if (partyCurrentHP[targetIdx] < 0) partyCurrentHP[targetIdx] = 0;
if (guiHP && guiHP[targetIdx]) guiHP[targetIdx].setText('❤ ' + partyCurrentHP[targetIdx]);
}
// Lose if all party members are at 0 HP
var allDead = true;
for (var i = 0; i < partyCurrentHP.length; i++) {
if (partyCurrentHP[i] > 0) allDead = false;
}
if (allDead) {
battleLose = true;
battleActive = false;
endBattle(false);
return;
}
}
}
// --- ENEMY ATTACK DELAY LOGIC ---
if (enemyAttackDelayActive) {
enemyAttackDelayTicks--;
if (enemyAttackDelayTicks <= 0) {
enemyAttackDelayActive = false;
}
return; // Don't attack until delay is over
}
// Enemy attack pattern
minorEnemyPatternTimer++;
if (minorEnemyPatternTimer % 40 === 0) {
spawnBulletPattern(minorEnemyPatternId);
// Minor enemy HP drops a bit each pattern
minorEnemyObj.hp--;
if (minorEnemyObj.hp < 0) minorEnemyObj.hp = 0;
if (minorEnemyHPText) minorEnemyHPText.setText('HP: ' + minorEnemyObj.hp);
}
// Win condition: survive or reduce enemy HP
if (minorEnemyObj.hp <= 0) {
// Next enemy or boss
cleanupMinorEnemyFight();
minorEnemyIdx++;
if (minorEnemyIdx < minorEnemyCount) {
startMinorEnemyFight(minorEnemyIdx);
} else {
// Proceed to boss
inMinorFight = false;
startBossFight();
}
}
}
function startBossFight() {
// Clean up minor enemy UI
if (minorEnemyObj) {
minorEnemyObj.destroy();
minorEnemyObj = null;
}
if (minorEnemyText) {
minorEnemyText.destroy();
minorEnemyText = null;
}
if (minorEnemyHPText) {
minorEnemyHPText.destroy();
minorEnemyHPText = null;
}
for (var i = battleBullets.length - 1; i >= 0; i--) {
battleBullets[i].destroy();
battleBullets.splice(i, 1);
}
LK.setScore(0);
// Show boss name and image
var bossNames = ['Amethyst Guardian', 'Emerald Warden', 'Amber Sentinel', 'Crimson Overlord', 'Obsidian Nemesis'];
var bossClasses = [AmethystBoss, EmeraldBoss, AmberBoss, CrimsonBoss, ObsidianBoss];
battleBossText = new Text2(bossNames[towerIdx], {
size: 100,
fill: '#fff'
});
battleBossText.anchor.set(0.5, 0);
battleBossText.x = 2048 / 2;
battleBossText.y = 200;
game.addChild(battleBossText);
// Show boss image
if (typeof battleBossSprite !== "undefined" && battleBossSprite) {
battleBossSprite.destroy();
battleBossSprite = null;
}
battleBossSprite = new bossClasses[towerIdx]();
battleBossSprite.x = 2048 / 2;
battleBossSprite.y = 500;
game.addChild(battleBossSprite);
// Show HP
battleHPText = new Text2('HP: ' + battleBossHP, {
size: 80,
fill: '#fff'
});
battleHPText.anchor.set(0.5, 0);
battleHPText.x = 2048 / 2;
battleHPText.y = 350;
game.addChild(battleHPText);
LK.setScore(0);
battleTimer = 0;
battlePhase = 0;
battleActive = true;
// Sword event variables
battleSwordEvent = null;
battleSwordEventTimer = 0;
// Reset enemy attack delay for boss
enemyAttackDelayTicks = 180;
enemyAttackDelayActive = true;
}
// Start music
if (towerIdx === 4) {
LK.playMusic('boss_theme');
} else {
LK.playMusic('battle_theme');
}
// Start first minor enemy
startMinorEnemyFight(0);
// Patch updateBattle to handle minor enemies before boss
var _originalUpdateBattle = updateBattle;
updateBattle = function updateBattle() {
if (gameState !== STATE_BATTLE || !battleActive) return;
if (inMinorFight) {
updateMinorEnemyFight();
return;
}
// Boss fight as before
// (player turn menu logic removed)
// Update heart
if (heart) heart.update();
// Update bullets
for (var i = battleBullets.length - 1; i >= 0; i--) {
var b = battleBullets[i];
b.update();
if (!b.active) {
battleBullets.splice(i, 1);
continue;
}
// Collision with heart
if (!heart.invincible && circleIntersect(heart.x, heart.y, heart.radius, b.x, b.y, b.radius)) {
heart.setInvincible(40);
LK.effects.flashObject(heart, 0xff0000, 400);
LK.getSound('hit').play();
// Randomly select a party member to take damage
if (partyCurrentHP.length > 0) {
var targetIdx = Math.floor(Math.random() * partyCurrentHP.length);
partyCurrentHP[targetIdx] -= 1;
if (partyCurrentHP[targetIdx] < 0) partyCurrentHP[targetIdx] = 0;
if (guiHP && guiHP[targetIdx]) guiHP[targetIdx].setText('❤ ' + partyCurrentHP[targetIdx]);
}
// Lose if all party members are at 0 HP
var allDead = true;
for (var i = 0; i < partyCurrentHP.length; i++) {
if (partyCurrentHP[i] > 0) allDead = false;
}
if (allDead) {
battleLose = true;
battleActive = false;
endBattle(false);
return;
}
}
}
// --- ENEMY ATTACK DELAY LOGIC ---
if (enemyAttackDelayActive) {
enemyAttackDelayTicks--;
if (enemyAttackDelayTicks <= 0) {
enemyAttackDelayActive = false;
}
return; // Don't attack until delay is over
}
// Boss attack pattern
battleTimer++;
if (battleBossId === 5) {
// Final boss: mix of all patterns
if (battleTimer % 30 === 0) {
spawnBulletPattern(battleTimer / 300 % 4 | 0);
}
} else {
if (battleTimer % 40 === 0) {
spawnBulletPattern(battleBossId - 1);
}
}
// --- Sword Event Logic ---
if (typeof battleSwordEventTimer === "undefined") battleSwordEventTimer = 0;
if (typeof battleSwordEvent === "undefined") battleSwordEvent = null;
// Only spawn sword event if boss fight is active
if (battleActive) {
// If no sword event, randomly spawn one every 180-300 ticks
if (!battleSwordEvent || !battleSwordEvent.active) {
battleSwordEventTimer++;
// Randomly spawn between 180 and 300 ticks
if (battleSwordEventTimer > 180 + Math.floor(Math.random() * 120)) {
battleSwordEventTimer = 0;
battleSwordEvent = new SwordEvent();
// Place sword randomly in heart box area, but always on the same row as the heart
var minX = 2048 / 2 - 300,
maxX = 2048 / 2 + 300;
battleSwordEvent.x = minX + Math.random() * (maxX - minX);
// Place sword at the same y as the heart (heart.y), or default to 1800 if heart is missing
battleSwordEvent.y = heart && typeof heart.y === "number" ? heart.y : 1800;
game.addChild(battleSwordEvent);
}
}
// Update sword event if present
if (battleSwordEvent && battleSwordEvent.active) {
battleSwordEvent.update();
// If heart touches sword, deal damage to boss and destroy sword
if (heart && !heart.invincible && circleIntersect(heart.x, heart.y, heart.radius, battleSwordEvent.x, battleSwordEvent.y, battleSwordEvent.radius)) {
battleBossHP -= 5;
if (battleBossHP < 0) battleBossHP = 0;
if (battleHPText) battleHPText.setText('HP: ' + Math.round(battleBossHP * 10) / 10);
LK.getSound('hit').play();
battleSwordEvent.active = false;
battleSwordEvent.destroy();
}
}
}
// Win condition: survive for N ticks or reduce boss HP
if (battleBossHP <= 0) {
battleWin = true;
battleActive = false;
endBattle(true);
return;
}
};
}
// Get orbs count
function getOrbsCount() {
var c = 0;
for (var i = 0; i < collectedOrbs.length; i++) if (collectedOrbs[i]) c++;
return c;
}
// Handle heart drag
function handleHeartDrag(x, y, obj) {
if (gameState !== STATE_BATTLE || !battleActive) return;
if (!heart) return;
// Clamp to battle box
var minX = 2048 / 2 - 350,
maxX = 2048 / 2 + 350;
var minY = 1200,
maxY = 2200;
var prevX = heart.x;
var prevY = heart.y;
heart.x = Math.max(minX, Math.min(maxX, x));
heart.y = Math.max(minY, Math.min(maxY, y));
// Shoot yellow bullet if moved (dragged)
if (typeof heart.lastShootTick === "undefined") heart.lastShootTick = 0;
if (typeof heartShootCooldown === "undefined") heartShootCooldown = 4;
if (Math.abs(heart.x - prevX) > 2 || Math.abs(heart.y - prevY) > 2) {
// Only shoot if enough ticks have passed since last shot
if (typeof LK !== "undefined" && LK.ticks - heart.lastShootTick > heartShootCooldown) {
var b = new HeartBullet();
b.init(heart.x, heart.y - heart.radius - 10);
if (typeof battleBullets !== "undefined") {
battleBullets.push(b);
}
if (typeof game !== "undefined") {
game.addChild(b);
}
heart.lastShootTick = LK.ticks;
}
}
}
// Handle heart down
function handleHeartDown(x, y, obj) {
if (gameState !== STATE_BATTLE || !battleActive) return;
if (!heart) return;
var dx = x - heart.x;
var dy = y - heart.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < heart.radius + 10) {
heartDragging = true;
heartDragOffsetX = dx;
heartDragOffsetY = dy;
}
}
// Handle heart up
function handleHeartUp(x, y, obj) {
if (gameState !== STATE_BATTLE || !battleActive) return;
heartDragging = false;
}
// --- BATTLE UPDATE ---
function updateBattle() {
if (gameState !== STATE_BATTLE || !battleActive) return;
// Update heart
if (heart) heart.update();
// Update bullets
for (var i = battleBullets.length - 1; i >= 0; i--) {
var b = battleBullets[i];
b.update();
if (!b.active) {
battleBullets.splice(i, 1);
continue;
}
// Heart bullet destroys enemy bullet
if (b instanceof HeartBullet) {
// Check collision with enemy bullets
for (var j = battleBullets.length - 1; j >= 0; j--) {
var eb = battleBullets[j];
if (eb === b) continue;
// Only destroy enemy bullets (not other heart bullets)
if (eb instanceof EnemyBullet && eb.active && b.active && circleIntersect(b.x, b.y, b.radius, eb.x, eb.y, eb.radius)) {
if (typeof eb.hp === "number" && eb.hp > 1) {
eb.hp--;
b.active = false;
b.destroy();
// Play hit sound for feedback
LK.getSound('hit').play();
// If hp drops to 0, destroy
if (eb.hp <= 0) {
eb.active = false;
eb.destroy();
battleBullets.splice(j, 1);
}
} else {
eb.active = false;
eb.destroy();
b.active = false;
b.destroy();
// Remove both from array
battleBullets.splice(j, 1);
}
break;
}
}
// Heart bullet hits boss
if (battleBossSprite && b.active && circleIntersect(b.x, b.y, b.radius, battleBossSprite.x, battleBossSprite.y, 90)) {
battleBossHP -= 0.5;
if (battleBossHP < 0) battleBossHP = 0;
if (battleHPText) battleHPText.setText('HP: ' + Math.round(battleBossHP * 10) / 10);
LK.getSound('hit').play();
b.active = false;
b.destroy();
}
if (!b.active) {
battleBullets.splice(i, 1);
continue;
}
}
// Collision with heart
if (!heart.invincible && b instanceof EnemyBullet && circleIntersect(heart.x, heart.y, heart.radius, b.x, b.y, b.radius)) {
// Hit!
heart.setInvincible(40);
LK.effects.flashObject(heart, 0xff0000, 400);
LK.getSound('hit').play();
LK.setScore(LK.getScore() + 1);
// Lose if hit 3 times
if (LK.getScore() >= 3) {
battleLose = true;
battleActive = false;
endBattle(false);
return;
}
}
}
// Boss attack pattern
battleTimer++;
if (battleBossId === 5) {
// Final boss: mix of all patterns
if (battleTimer % 30 === 0) {
spawnBulletPattern(battleTimer / 300 % 4 | 0);
}
} else {
if (battleTimer % 40 === 0) {
spawnBulletPattern(battleBossId - 1);
}
}
// Win condition: survive for N ticks or reduce boss HP
if (battleBossHP <= 0) {
battleWin = true;
battleActive = false;
endBattle(true);
return;
}
}
// --- BATTLE PATTERNS ---
function spawnBulletPattern(patternId) {
var cx = 2048 / 2,
cy = 1700;
// Determine EX mode bullet speed multiplier
var exSpeed = typeof game !== "undefined" && game._exModeActive ? 1.6 : 1;
if (patternId === 0) {
// Simple downward rain
for (var i = 0; i < 5; i++) {
var b = new EnemyBullet();
b.init(cx - 300 + 150 * i, 1200, 0, 12 * exSpeed, true); // true = boss fight bullet
battleBullets.push(b);
game.addChild(b);
}
} else if (patternId === 1) {
// Sine wave
for (var i = 0; i < 4; i++) {
var b = new EnemyBullet();
b.init(cx - 200 + 200 * i, 1200, Math.sin(LK.ticks / 20 + i) * 6 * exSpeed, 10 * exSpeed, true);
battleBullets.push(b);
game.addChild(b);
}
} else if (patternId === 2) {
// Spiral
var angle = LK.ticks % 360 / 180 * Math.PI;
for (var i = 0; i < 6; i++) {
var a = angle + i * Math.PI / 3;
var b = new EnemyBullet();
b.init(cx, 1400, Math.cos(a) * 8 * exSpeed, (Math.sin(a) * 8 + 8) * exSpeed, true);
battleBullets.push(b);
game.addChild(b);
}
} else if (patternId === 3) {
// Random bursts
for (var i = 0; i < 8; i++) {
var a = Math.random() * Math.PI * 2;
var b = new EnemyBullet();
b.init(cx, 1500, Math.cos(a) * 10 * exSpeed, (Math.sin(a) * 10 + 10) * exSpeed, true);
battleBullets.push(b);
game.addChild(b);
}
}
// Boss HP drops a bit each pattern
battleBossHP--;
if (battleBossHP < 0) battleBossHP = 0;
if (battleHPText) battleHPText.setText('HP: ' + Math.round(battleBossHP * 10) / 10);
}
// --- END BATTLE ---
function endBattle(win) {
if (win) {
LK.getSound('boss_defeat').play();
// Collect orb if not final boss
if (battleBossId <= 4) {
collectedOrbs[battleBossId - 1] = true;
// Save orbs persistently
storage.collectedOrbs = collectedOrbs.slice();
LK.getSound('collect_orb').play();
}
// Show win text
var winText = new Text2('Victory!', {
size: 140,
fill: '#fff'
});
winText.anchor.set(0.5, 0.5);
winText.x = 2048 / 2;
winText.y = 1200;
game.addChild(winText);
LK.effects.flashScreen(0x00ff00, 800);
battleEndTimeout = LK.setTimeout(function () {
winText.destroy();
cleanupBattle();
if (battleBossId === 5) {
// Game complete
showGameComplete();
} else {
startTowerSelect();
}
}, 1800);
} else {
// Lose
var loseText = new Text2('Defeated!', {
size: 140,
fill: '#fff'
});
loseText.anchor.set(0.5, 0.5);
loseText.x = 2048 / 2;
loseText.y = 1200;
game.addChild(loseText);
LK.effects.flashScreen(0xff0000, 800);
battleEndTimeout = LK.setTimeout(function () {
loseText.destroy();
cleanupBattle();
startTowerSelect();
}, 1800);
}
}
// --- CLEANUP BATTLE ---
function cleanupBattle() {
if (heart) {
heart.destroy();
heart = null;
}
// Remove heartBox if present
if (typeof heartBox !== "undefined" && heartBox) {
heartBox.destroy();
heartBox = null;
}
for (var i = 0; i < battleBullets.length; i++) battleBullets[i].destroy();
battleBullets = [];
if (battleBossText) {
battleBossText.destroy();
battleBossText = null;
}
// Remove sword event if present
if (typeof battleSwordEvent !== "undefined" && battleSwordEvent) {
battleSwordEvent.destroy();
battleSwordEvent = null;
}
// Remove boss sprite if present
if (typeof battleBossSprite !== "undefined" && battleBossSprite) {
battleBossSprite.destroy();
battleBossSprite = null;
}
if (battleHPText) {
battleHPText.destroy();
battleHPText = null;
}
if (battleOrbsText) {
battleOrbsText.destroy();
battleOrbsText = null;
}
for (var i = 0; i < guiParty.length; i++) guiParty[i].destroy();
guiParty = [];
for (var i = 0; i < guiOrbs.length; i++) guiOrbs[i].destroy();
guiOrbs = [];
if (guiHP) {
if (Array.isArray(guiHP)) {
for (var i = 0; i < guiHP.length; i++) {
if (guiHP[i]) guiHP[i].destroy();
}
} else {
guiHP.destroy();
}
guiHP = null;
}
LK.setScore(0);
LK.stopMusic();
}
// --- GAME COMPLETE ---
function showGameComplete() {
clearCharacterStatsAndReset();
gameState = STATE_GAME_COMPLETE;
var completeText = new Text2('You conquered all towers!\nQuest Complete!', {
size: 120,
fill: '#fff'
});
completeText.anchor.set(0.5, 0.5);
completeText.x = 2048 / 2;
completeText.y = 1200;
game.addChild(completeText);
LK.effects.flashScreen(0x00ffcc, 1200);
LK.setTimeout(function () {
completeText.destroy();
// Offer EX Tower mode if not already in EX mode
if (!game._exModeActive) {
// Show EX mode prompt
var exText = new Text2('Secret EX Towers unlocked!\nTap to challenge EX Towers!', {
size: 100,
fill: '#ff3333'
});
exText.anchor.set(0.5, 0.5);
exText.x = 2048 / 2;
exText.y = 1200;
game.addChild(exText);
// Set up tap to start EX mode
var _exModeHandler = function exModeHandler(x, y, obj) {
if (exText && exText.parent) {
var bx = exText.x - exText.width * exText.anchor.x;
var by = exText.y - exText.height * exText.anchor.y;
var bw = exText.width;
var bh = exText.height;
if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) {
exText.destroy();
game.off('down', _exModeHandler);
startEXMode();
}
}
};
game.on('down', _exModeHandler);
} else {
// If already in EX mode, reset to normal game
resetGame();
}
}, 3000);
}
// --- EX TOWER MODE ---
function startEXMode() {
// Mark EX mode active
game._exModeActive = true;
// Reset all state, but mark EX mode
gameState = STATE_TOWER_SELECT;
selectedParty = [];
// Keep orbs and party, but reset towers
for (var i = 0; i < partyIcons.length; i++) partyIcons[i].destroy();
partyIcons = [];
for (var i = 0; i < towerIcons.length; i++) towerIcons[i].destroy();
towerIcons = [];
for (var i = 0; i < orbIcons.length; i++) orbIcons[i].destroy();
orbIcons = [];
if (partySelectText) {
partySelectText.destroy();
partySelectText = null;
}
if (towerSelectText) {
towerSelectText.destroy();
towerSelectText = null;
}
if (heart) {
heart.destroy();
heart = null;
}
for (var i = 0; i < battleBullets.length; i++) battleBullets[i].destroy();
battleBullets = [];
if (battleBossText) {
battleBossText.destroy();
battleBossText = null;
}
if (battleHPText) {
battleHPText.destroy();
battleHPText = null;
}
if (battleOrbsText) {
battleOrbsText.destroy();
battleOrbsText = null;
}
if (battleEndTimeout) {
LK.clearTimeout(battleEndTimeout);
battleEndTimeout = null;
}
for (var i = 0; i < guiParty.length; i++) guiParty[i].destroy();
guiParty = [];
for (var i = 0; i < guiOrbs.length; i++) guiOrbs[i].destroy();
guiOrbs = [];
if (guiHP) {
if (Array.isArray(guiHP)) {
for (var i = 0; i < guiHP.length; i++) {
if (guiHP[i]) guiHP[i].destroy();
}
} else {
guiHP.destroy();
}
guiHP = null;
}
LK.setScore(0);
// Show EX tower select
startTowerSelect();
}
// --- CIRCLE INTERSECT ---
function circleIntersect(x1, y1, r1, x2, y2, r2) {
var dx = x1 - x2,
dy = y1 - y2;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < r1 + r2 - 10;
}
// --- GAME EVENTS ---
game.down = function (x, y, obj) {
// Check for reset button tap
if (resetButton && resetButton.parent) {
var btn = resetButton;
var bx = btn.x - btn.width * btn.anchor.x;
var by = btn.y - btn.height * btn.anchor.y;
var bw = btn.width;
var bh = btn.height;
// Convert to LK.gui.bottom coordinates
var local = LK.gui.bottom.toLocal({
x: x,
y: y
});
if (local.x >= bx && local.x <= bx + bw && local.y >= by && local.y <= by + bh) {
resetGame();
return;
}
}
// (player turn menu and shop/magic menu input handling removed)
if (gameState === STATE_PARTY_SELECT) {
handlePartySelect(x, y, obj);
} else if (gameState === STATE_TOWER_SELECT) {
handleTowerSelect(x, y, obj);
} else if (gameState === STATE_BATTLE && battleActive) {
handleHeartDown(x, y, obj);
}
};
game.move = function (x, y, obj) {
if (gameState === STATE_BATTLE && battleActive && heartDragging) {
handleHeartDrag(x - heartDragOffsetX, y - heartDragOffsetY, obj);
}
};
game.up = function (x, y, obj) {
if (gameState === STATE_BATTLE && battleActive) {
handleHeartUp(x, y, obj);
}
};
// --- GAME UPDATE ---
game.update = function () {
if (gameState === STATE_BATTLE && battleActive) {
updateBattle();
}
};
// --- START GAME ---
LK.playMusic('main_theme');
resetGame();
// Show username at the bottom of the screen
if (typeof usernameText !== "undefined" && usernameText) {
usernameText.destroy();
usernameText = null;
}
var username = storage.username || "Player";
var usernameText = new Text2(username, {
size: 60,
fill: "#fff"
});
usernameText.anchor.set(0.5, 1);
usernameText.x = 2048 / 2;
usernameText.y = 2732 - 20;
LK.gui.bottom.addChild(usernameText); ===================================================================
--- original.js
+++ change.js
@@ -325,8 +325,17 @@
self.icon = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 1
});
+ // Tint red if EX mode is active
+ if (typeof game !== "undefined" && game._exModeActive) {
+ // Use tween for instant tint
+ tween(self.icon, {
+ tint: 0xFF3333
+ }, {
+ duration: 0
+ });
+ }
self.icon.alpha = locked ? 0.4 : 1;
};
return self;
});
@@ -672,8 +681,20 @@
icon.y = 700;
icon.towerIndex = i;
towerIcons.push(icon);
game.addChild(icon);
+ // Add EX label if in EX mode
+ if (typeof game !== "undefined" && game._exModeActive) {
+ var exLabel = new Text2("EX", {
+ size: 60,
+ fill: 0xFF3333
+ });
+ exLabel.anchor.set(0.5, 0.5);
+ exLabel.x = icon.x;
+ exLabel.y = icon.y - 180;
+ game.addChild(exLabel);
+ icon._exLabel = exLabel;
+ }
}
// Show orbs collected
for (var i = 0; i < 4; i++) {
var orb = new OrbIcon();
@@ -720,9 +741,14 @@
battleLose = false;
battlePhase = 0;
battleTimer = 0;
battleBossId = towerIdx + 1;
- battleBossHP = 40 + towerIdx * 20;
+ if (typeof game !== "undefined" && game._exModeActive) {
+ // EX mode: more HP
+ battleBossHP = (40 + towerIdx * 20) * 2.2;
+ } else {
+ battleBossHP = 40 + towerIdx * 20;
+ }
battleBossMaxHP = battleBossHP;
battleBullets = [];
// Initialize each party member's HP
partyCurrentHP = [];
@@ -1363,21 +1389,23 @@
// --- BATTLE PATTERNS ---
function spawnBulletPattern(patternId) {
var cx = 2048 / 2,
cy = 1700;
+ // Determine EX mode bullet speed multiplier
+ var exSpeed = typeof game !== "undefined" && game._exModeActive ? 1.6 : 1;
if (patternId === 0) {
// Simple downward rain
for (var i = 0; i < 5; i++) {
var b = new EnemyBullet();
- b.init(cx - 300 + 150 * i, 1200, 0, 12, true); // true = boss fight bullet
+ b.init(cx - 300 + 150 * i, 1200, 0, 12 * exSpeed, true); // true = boss fight bullet
battleBullets.push(b);
game.addChild(b);
}
} else if (patternId === 1) {
// Sine wave
for (var i = 0; i < 4; i++) {
var b = new EnemyBullet();
- b.init(cx - 200 + 200 * i, 1200, Math.sin(LK.ticks / 20 + i) * 6, 10, true);
+ b.init(cx - 200 + 200 * i, 1200, Math.sin(LK.ticks / 20 + i) * 6 * exSpeed, 10 * exSpeed, true);
battleBullets.push(b);
game.addChild(b);
}
} else if (patternId === 2) {
@@ -1385,18 +1413,18 @@
var angle = LK.ticks % 360 / 180 * Math.PI;
for (var i = 0; i < 6; i++) {
var a = angle + i * Math.PI / 3;
var b = new EnemyBullet();
- b.init(cx, 1400, Math.cos(a) * 8, Math.sin(a) * 8 + 8, true);
+ b.init(cx, 1400, Math.cos(a) * 8 * exSpeed, (Math.sin(a) * 8 + 8) * exSpeed, true);
battleBullets.push(b);
game.addChild(b);
}
} else if (patternId === 3) {
// Random bursts
for (var i = 0; i < 8; i++) {
var a = Math.random() * Math.PI * 2;
var b = new EnemyBullet();
- b.init(cx, 1500, Math.cos(a) * 10, Math.sin(a) * 10 + 10, true);
+ b.init(cx, 1500, Math.cos(a) * 10 * exSpeed, (Math.sin(a) * 10 + 10) * exSpeed, true);
battleBullets.push(b);
game.addChild(b);
}
}
@@ -1520,11 +1548,102 @@
game.addChild(completeText);
LK.effects.flashScreen(0x00ffcc, 1200);
LK.setTimeout(function () {
completeText.destroy();
- resetGame();
+ // Offer EX Tower mode if not already in EX mode
+ if (!game._exModeActive) {
+ // Show EX mode prompt
+ var exText = new Text2('Secret EX Towers unlocked!\nTap to challenge EX Towers!', {
+ size: 100,
+ fill: '#ff3333'
+ });
+ exText.anchor.set(0.5, 0.5);
+ exText.x = 2048 / 2;
+ exText.y = 1200;
+ game.addChild(exText);
+ // Set up tap to start EX mode
+ var _exModeHandler = function exModeHandler(x, y, obj) {
+ if (exText && exText.parent) {
+ var bx = exText.x - exText.width * exText.anchor.x;
+ var by = exText.y - exText.height * exText.anchor.y;
+ var bw = exText.width;
+ var bh = exText.height;
+ if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) {
+ exText.destroy();
+ game.off('down', _exModeHandler);
+ startEXMode();
+ }
+ }
+ };
+ game.on('down', _exModeHandler);
+ } else {
+ // If already in EX mode, reset to normal game
+ resetGame();
+ }
}, 3000);
}
+// --- EX TOWER MODE ---
+function startEXMode() {
+ // Mark EX mode active
+ game._exModeActive = true;
+ // Reset all state, but mark EX mode
+ gameState = STATE_TOWER_SELECT;
+ selectedParty = [];
+ // Keep orbs and party, but reset towers
+ for (var i = 0; i < partyIcons.length; i++) partyIcons[i].destroy();
+ partyIcons = [];
+ for (var i = 0; i < towerIcons.length; i++) towerIcons[i].destroy();
+ towerIcons = [];
+ for (var i = 0; i < orbIcons.length; i++) orbIcons[i].destroy();
+ orbIcons = [];
+ if (partySelectText) {
+ partySelectText.destroy();
+ partySelectText = null;
+ }
+ if (towerSelectText) {
+ towerSelectText.destroy();
+ towerSelectText = null;
+ }
+ if (heart) {
+ heart.destroy();
+ heart = null;
+ }
+ for (var i = 0; i < battleBullets.length; i++) battleBullets[i].destroy();
+ battleBullets = [];
+ if (battleBossText) {
+ battleBossText.destroy();
+ battleBossText = null;
+ }
+ if (battleHPText) {
+ battleHPText.destroy();
+ battleHPText = null;
+ }
+ if (battleOrbsText) {
+ battleOrbsText.destroy();
+ battleOrbsText = null;
+ }
+ if (battleEndTimeout) {
+ LK.clearTimeout(battleEndTimeout);
+ battleEndTimeout = null;
+ }
+ for (var i = 0; i < guiParty.length; i++) guiParty[i].destroy();
+ guiParty = [];
+ for (var i = 0; i < guiOrbs.length; i++) guiOrbs[i].destroy();
+ guiOrbs = [];
+ if (guiHP) {
+ if (Array.isArray(guiHP)) {
+ for (var i = 0; i < guiHP.length; i++) {
+ if (guiHP[i]) guiHP[i].destroy();
+ }
+ } else {
+ guiHP.destroy();
+ }
+ guiHP = null;
+ }
+ LK.setScore(0);
+ // Show EX tower select
+ startTowerSelect();
+}
// --- CIRCLE INTERSECT ---
function circleIntersect(x1, y1, r1, x2, y2, r2) {
var dx = x1 - x2,
dy = y1 - y2;
8-bit heart. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
martial artist. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
8- bit girl thief. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
White mage 8-bit girl. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A diamond shaped white bullet. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
purple orb. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
green emerald. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
orange diamond . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
red crystal. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
make a 8-bit knight. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
8-bit RPG tower. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Green Tower. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A tree with a door on top of a hill. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A castle surrounded by red hot lava. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a twisted castle with creepy trees on the ground. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a realistic whisp of blue fire. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a goblin with a dagger. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
realistic slime monster. In-Game asset. 2d. High contrast. No shadows
a realistic rat. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a red chest mimic. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Realistic Imp. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a pile of money that is also a mimic and realistic. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a brick mimic realistic. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a goblin with a dagger realistic. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a realistic bat. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a white bullet. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a giant realistic monster made out of obsidian. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a king made out of emerald. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a red ruby monster with a two handed greatsword. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
amethyst knight. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
final boss holding a white mage from the game and the knight from the game. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a sword on a yellow button. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat