User prompt
Please fix the bug: 'Uncaught TypeError: Failed to execute 'attachShader' on 'WebGLRenderingContext': parameter 2 is not of type 'WebGLShader'.' in or related to this line: 'return;' Line Number: 1776
User prompt
Add a counter to see how many villages I raided so far
User prompt
Add new enemy [human] units: Melee: King's guard (appears on village 15); Ranged: Crossbowman(appears on village 16); Defender: King's shield (appears on village 23); Artillery: Bomber(appears on village 13), Cannoneer (appears on village 20), trebuchet (appears on village 26)
User prompt
Add a new thingy: Defence towers! After beating 10 villages, the humans will add defensive towers to destroy your defenses! [Towers: archer tower, bomb tower, heal tower]
User prompt
Add a new mode: Boss hunting! After raiding 5 villages you can Fight bosses to win gold! The stronger the boss, the more gold you get! (Bosses: giant farmer, giant guard, giant elite guard)
User prompt
Please fix the bug: 'researchDefenderButton is not defined' in or related to this line: 'researchDefenderButton.x = -buttonXOffsetSmall; // Mid Left' Line Number: 873
User prompt
Add a new type of troop: Artillery! A ranged with high range and high attack, but is really fragile and slow. Protect it as much as you can. To unlock this unit, you need reasearch (Units [from weakest to stronghest]; Artillery: Skeleton bomber, Pumpkin catapult, skeleton dragon)
User prompt
Piggies now give 35 gold
User prompt
Add new skeleton units: Melee: Elite swordsman skeleton; Ranged: Crossbowman skeleton; Defender: Dark knight skeleton (note that these units are stronger than their previous counterparts, and are also of the final tier)
User prompt
Add new skeleton units: Melee: Elite swordsman skeleton; Ranged: Crossbowman skeleton; Defender: Dark knight skeleton (note that these units are stronger than their previous counterparts, and are also of the final tier)
User prompt
Gold piggies now only give 25 gold
User prompt
Add new humans: Local protector (starts appearing on teh 6th village), Guardian (starts appearing on the 9th village)
User prompt
Golden piggy only spawns of the first village; fix that
User prompt
Sometimes in battle a golden piggy appears; if you tap it, you will get 50 gold!
User prompt
Sometimes in battle a golden piggy appears; if you tap it, you will get 50 gold!
User prompt
Defender has the same speed as melee
User prompt
Space the recruitment buttons out
User prompt
Add a button to recruit Defender type units
User prompt
Add new type of unit: Defender! Defender type units are melee type units that have high health but have low damage; humans don't have them yet, but your army does! (Unlocking this class requires research; Units [from weakest to stronghest]: Shielded skeleton, Skeleton holpite)]
User prompt
Add new type of unit: Defender! Defender type units are melee type units that have high health but have low damage; humans don't have them yet, but your army does! (Unlocking this class requires research; Units [from weakest to stronghest]: Shielded skeleton, Skeleton holpite)]
User prompt
Add new enemies: Melee: human elite guard; Ranged: human archer
User prompt
Units never get faster
User prompt
Units never get faster
User prompt
Skeleton swordsman uses the asset skeletonSwordsman and Skeleton archer uses asset skeletonArcher
User prompt
Add new units: melee: Skeleton swordsman; Ranged: Skeleton archer. Note that these units are stronger than rookie and trainee archer, so they can be researched AFTER researching Rookie or trainee
/**** * Classes ****/ // Ensure ranged sound exists // Simple Button Class var Button = Container.expand(function (text, config) { var self = Container.call(this); // Methods must be defined before event handlers if called internally self.setText = function (newText) { self.label.setText(newText); }; self.setEnabled = function (enabled) { self.interactive = enabled; // Control interactability // LK engine doesn't directly use obj.interactive for event routing like standard PIXI, // but good practice. Use alpha for visual cue. self.alpha = enabled ? 1.0 : 0.5; }; // --- Initialize --- self.config = config || {}; var width = self.config.width || 300; var height = self.config.height || 100; var bgColor = self.config.bgColor || 0x555555; var textColor = self.config.textColor || '#FFFFFF'; var textSize = self.config.textSize || 40; var bg = self.attachAsset('buttonBg', { width: width, height: height, // color: bgColor, // Shape color is set at init time, cannot override here anchorX: 0.5, anchorY: 0.5 }); // Tint the background shape if a color was provided in config bg.tint = bgColor; self.label = new Text2(text, { size: textSize, fill: textColor }); self.label.anchor.set(0.5, 0.5); self.addChild(self.label); // Placeholder for the action, assign this after creating an instance self.onClick = null; // Must be assigned externally // LK engine automatically calls self.down if attached and interactive self.down = function (x, y, obj) { if (!self.interactive) { return; } // Respect the enabled state // Visual feedback for press self.scale.set(0.95); }; // LK engine automatically calls self.up if attached and interactive self.up = function (x, y, obj) { if (!self.interactive) { return; } // Respect the enabled state self.scale.set(1.0); // Restore scale // Check bounds roughly (optional, prevents firing if dragged off significantly) if (x >= -width / 2 && x <= width / 2 && y >= -height / 2 && y <= height / 2) { if (self.onClick) { self.onClick(); // Execute the assigned action } } }; // Initial state self.interactive = true; // Default to enabled return self; }); // Class for the Golden Piggy var GoldenPiggy = Container.expand(function () { var self = Container.call(this); // Constants var LIFETIME_TICKS = 5 * 60; // 5 seconds lifespan (at 60 FPS) // Properties self.ticksAlive = 0; self.isCollected = false; // --- Methods --- // Called when the piggy is tapped/clicked self.collect = function () { if (self.isCollected) { return; } // Prevent double collection self.isCollected = true; gold += 35; // Updated gold amount updateGoldDisplay(); // Optional: Play a sound effect try { // Reuse recruit sound or add a dedicated 'coin' sound if available LK.getSound('recruit').play(); } catch (e) { console.log("Sound error playing piggy collect sound: " + e); } // Remove the piggy visually (can be done in game.update) // self.visible = false; // Or self.destroy() handled by game.update console.log("Golden Piggy collected! +50 gold."); }; // LK engine calls this automatically if attached self.up = function (x, y, obj) { // Check bounds roughly to ensure the tap was on the piggy // Assuming piggy is roughly 100x100 as per asset if (x >= -self.graphics.width / 2 && x <= self.graphics.width / 2 && y >= -self.graphics.height / 2 && y <= self.graphics.height / 2) { self.collect(); } }; // Called by game.update self.update = function () { if (self.isCollected) { return; // Stop updating if collected } self.ticksAlive++; if (self.ticksAlive > LIFETIME_TICKS) { // Mark for removal if lifetime expires self.isCollected = true; // Use the same flag to trigger removal console.log("Golden Piggy disappeared."); } // Optional: Add subtle movement like bobbing up and down self.graphics.y = Math.sin(self.ticksAlive / 20) * 10; // Bobbing effect }; // --- Initialize --- self.graphics = self.attachAsset('Goldpiggy', { anchorX: 0.5, anchorY: 0.5 }); self.interactive = true; // Make it tappable return self; }); // var tween = LK.import('@upit/tween.v1'); // Not strictly needed for MVP // Base class for all units var Unit = Container.expand(function (config) { var self = Container.call(this); // Define methods first self.findTarget = function (enemyArray) { var closestTarget = null; var minDistance = Infinity; // Make sure enemyArray is defined and is an array if (!enemyArray || !Array.isArray(enemyArray)) { return null; } for (var i = 0; i < enemyArray.length; i++) { var enemy = enemyArray[i]; if (enemy.isDead) { continue; } // Using horizontal distance for simplicity var distance = Math.abs(self.x - enemy.x); if (distance < minDistance) { minDistance = distance; closestTarget = enemy; } } if (closestTarget && minDistance <= self.attackRange) { return closestTarget; // Return target if it's within attack range } else { return null; // No target in range } }; self.attack = function () { if (self.target && !self.target.isDead && self.attackCooldown <= 0) { try { // Defensive coding for sound playback LK.getSound(self.attackSoundId).play(); } catch (e) { console.log("Sound error playing " + self.attackSoundId + ": " + e); } self.target.takeDamage(self.attackPower); self.attackCooldown = self.attackCooldownTime; } }; self.move = function () { // Move towards the enemy side self.x += self.speed * (self.isEnemy ? -1 : 1); // Basic boundary check if (self.x < -100 || self.x > GAME_WIDTH + 100) { self.die(); // Remove if goes way off screen } }; self.takeDamage = function (damage) { if (self.isDead) { return; } // Already dead self.hp -= damage; // Optional: Add a visual effect like tinting red briefly // LK.effects.flashObject(self, 0xFF0000, 100); // Flashing the container might work if (self.hp <= 0) { // Award gold if an enemy dies from player attack if (self.isEnemy) { // Award gold based on unit type/strength gold += 5; // Base gold reward updateGoldDisplay(); // Update the display immediately } self.die(); } }; self.die = function () { if (!self.isDead) { try { // Defensive coding for sound playback LK.getSound('unitDeath').play(); } catch (e) { console.log("Sound error playing unitDeath: " + e); } self.isDead = true; // It will be removed from the game and array in game.update } }; // LK calls this automatically if the instance is attached to the stage/game self.update = function (enemyArray) { // This method requires the global `isBattling` to be accessible. if (self.isDead || !isBattling) { return; } // Don't update if dead or battle not active // Update cooldown if (self.attackCooldown > 0) { self.attackCooldown--; } // Find target self.target = self.findTarget(enemyArray); // Act based on target if (self.target) { self.isMoving = false; // Stop moving when target is in range self.attack(); } else { self.isMoving = true; // No target in range, resume moving } // Move if needed if (self.isMoving) { self.move(); } self.lastX = self.x; // Update last known state }; // --- Initialize --- self.isEnemy = config.isEnemy; self.hp = config.hp; self.maxHp = config.hp; // Store max HP self.attackPower = config.attackPower; self.attackRange = config.attackRange; self.attackCooldownTime = config.attackCooldownTime || 60; // Default 1 sec cooldown self.speed = config.speed; self.assetId = config.assetId; self.attackSoundId = config.attackSoundId || 'attackMelee'; self.unitType = config.unitType; // Store the unit type identifier // Attach graphics *after* properties are set self.graphics = self.attachAsset(self.assetId, { anchorX: 0.5, anchorY: 0.5 }); // State variables self.target = null; self.attackCooldown = 0; // Start ready to attack self.isMoving = true; self.isDead = false; self.lastX = 0; // Initialize lastX, will be set correctly on spawn // Upgrade logic removed - replaced by global research system }); // WebGL Shader initialization var WebGLHelper = Container.expand(function () { var self = Container.call(this); // Method to initialize WebGL shaders self.initShaders = function (renderer) { if (!renderer || !renderer.gl) { return; } try { var createShader = function createShader(gl, source, type) { var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('WebGL shader compilation failed: ' + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; }; var gl = renderer.gl; // Vertex shader var vertexShaderSource = "\n\t\t\t\tattribute vec2 aVertexPosition;\n\t\t\t\tattribute vec2 aTextureCoord;\n\t\t\t\tuniform mat3 projectionMatrix;\n\t\t\t\tvarying vec2 vTextureCoord;\n\t\t\t\tvoid main(void) {\n\t\t\t\t\tgl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n\t\t\t\t\tvTextureCoord = aTextureCoord;\n\t\t\t\t}"; // Fragment shader var fragmentShaderSource = "\n\t\t\t\tprecision mediump float;\n\t\t\t\tvarying vec2 vTextureCoord;\n\t\t\t\tuniform sampler2D uSampler;\n\t\t\t\tvoid main(void) {\n\t\t\t\t\tgl_FragColor = texture2D(uSampler, vTextureCoord);\n\t\t\t\t}"; // Create shader program var vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER); var fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER); if (!vertexShader || !fragmentShader) { return; } // Create program var program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('WebGL program linking failed: ' + gl.getProgramInfoLog(program)); gl.deleteProgram(program); return; } return program; } catch (e) { console.error('WebGL shader initialization error:', e); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x101020 // Dark background for the war theme }); /**** * Game Code ****/ // Button background // Blue ellipse // Green box // Gray ellipse // White box // Define logical assets for units and UI. // LK Engine automatically creates assets based on usage. // Game constants and configuration // Added assets for new human units // Placeholder ID // Placeholder ID // Placeholder ID // Ensure melee sound exists var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLAYER_SPAWN_X = 150; var ENEMY_SPAWN_X = GAME_WIDTH - 150; var SPAWN_Y_START = GAME_HEIGHT * 0.3; // Start spawning units lower down var SPAWN_Y_END = GAME_HEIGHT * 0.8; // End spawning units lower down var SPAWN_Y_STEP = 100; // Vertical distance between spawned units // Research configuration var MILITIA_RESEARCH_COST = 50; var SLINGER_RESEARCH_COST = 75; var SHIELDED_RESEARCH_COST = 60; // Cost to research Shielded Skeleton var SWORDSMAN_RESEARCH_COST = 120; // Cost to research Swordsman var ARCHER_RESEARCH_COST = 150; // Cost to research Archer var HOPLITE_RESEARCH_COST = 180; // Cost to research Hoplite var militiaResearchComplete = false; // Tier 1 Melee (Rookie) var slingerResearchComplete = false; // Tier 1 Ranged (Trainee) var swordsmanResearchComplete = false; // Tier 2 Melee var archerResearchComplete = false; // Tier 2 Ranged var shieldedResearchComplete = false; // Tier 1 Defender var hopliteResearchComplete = false; // Tier 2 Defender // Unit stats lookup table var UNIT_STATS = { skeletonMilitia: { hp: 50, attackPower: 5, attackRange: 80, speed: 1.5, assetId: 'skeletonMilitia', cost: 10, // Cost to recruit base militia attackSoundId: 'attackMelee' }, // Note: skeletonRookie stats are defined below, its cost is for recruitment *after* research. skeletonSlinger: { hp: 30, attackPower: 7, attackRange: 400, speed: 1.2, assetId: 'skeletonSlinger', cost: 15, // Cost to recruit base slinger attackCooldownTime: 90, attackSoundId: 'attackRanged' }, // Note: skeletonTraineeArcher stats are defined below, its cost is for recruitment *after* research. humanFarmer: { hp: 40, attackPower: 4, attackRange: 70, speed: 1.3, assetId: 'humanFarmer', attackSoundId: 'attackMelee' }, humanSlinger: { hp: 25, attackPower: 6, attackRange: 380, speed: 1.1, assetId: 'humanSlinger', attackCooldownTime: 90, attackSoundId: 'attackRanged' }, // --- New Human Units --- humanGuard: { hp: 80, // Tougher than Farmer attackPower: 7, attackRange: 85, // Slightly longer reach than militia speed: 1.2, assetId: 'humanGuard', attackSoundId: 'attackMelee' }, humanMilitiaArcher: { hp: 35, // Similar to Slinger attackPower: 8, // Slightly more damage than Slinger attackRange: 420, // Slightly longer range speed: 1.3, assetId: 'humanMilitiaArcher', attackCooldownTime: 80, // Slightly faster fire rate attackSoundId: 'attackRanged' }, // --- New Elite Human Units --- humanEliteGuard: { hp: 120, // Much tougher than Guard attackPower: 12, // Significantly more damage than Guard attackRange: 90, // Slightly longer reach than Guard speed: 1.2, // Same speed as Guard assetId: 'humanEliteguard', attackSoundId: 'attackMelee' }, humanArcher: { hp: 45, // Tougher than Militia Archer attackPower: 11, // More damage than Militia Archer attackRange: 450, // Better range than Militia Archer speed: 1.3, // Same speed as Militia Archer assetId: 'HumanArcher', attackCooldownTime: 70, // Faster fire rate than Militia Archer attackSoundId: 'attackRanged' }, // --- New King's Guard (Melee Tier 3) --- "King's guard": { hp: 180, // Very tough attackPower: 15, // High damage attackRange: 100, // Good reach speed: 1.4, // Slightly faster than Elite Guard assetId: 'humanEliteguard', // Reusing asset, could be new if available attackSoundId: 'attackMelee' }, // --- New Crossbowman (Ranged Tier 3) --- "Crossbowman": { hp: 60, // Fragile attackPower: 18, // Very high damage attackRange: 500, // Long range speed: 1.1, // Slower than Archer assetId: 'skeletonCrossbowman', // Reusing asset, could be new if available attackCooldownTime: 60, // Fast attack attackSoundId: 'attackRanged' }, // --- New King's Shield (Defender Tier 3) --- "King's shield": { hp: 300, // Extremely tough attackPower: 8, // Moderate damage attackRange: 110, // Excellent reach speed: 1.3, // Average speed for defender assetId: 'humanGuardian', // Reusing asset, could be new if available attackSoundId: 'attackMelee' }, // --- New Bomber (Artillery Tier 1) --- "Bomber": { hp: 20, // Very fragile attackPower: 20, // High AoE damage (conceptually) attackRange: 700, // Very long range speed: 0.6, // Very slow assetId: 'skeletonBomber', // Reusing asset, could be new if available attackCooldownTime: 180, // Very slow attack rate attackSoundId: 'attackRanged' // Placeholder, ideally an explosion sound }, // --- New Cannoneer (Artillery Tier 2) --- "Cannoneer": { hp: 35, // Fragile attackPower: 30, // Very high AoE damage attackRange: 800, // Extremely long range speed: 0.5, // Even slower assetId: 'skeletonPumpkinCatapult', // Reusing asset, could be new if available attackCooldownTime: 240, // Very slow attack rate attackSoundId: 'attackRanged' // Placeholder }, // --- New Trebuchet (Artillery Tier 3) --- "Trebuchet": { hp: 50, // Still fragile attackPower: 50, // Massive AoE damage attackRange: 900, // Supreme range speed: 0.4, // Extremely slow assetId: 'skeletonDragon', // Reusing asset, could be new if available attackCooldownTime: 300, // Extremely slow attack rate attackSoundId: 'attackRanged' // Placeholder }, // -- Special Human Units -- humanLocalprotector: { hp: 150, // Very tough defender unit attackPower: 6, // Moderate damage attackRange: 90, // Melee range speed: 1.5, // Same speed as melee units assetId: 'humanLocalprotector', attackSoundId: 'attackMelee' }, humanGuardian: { hp: 220, // Extremely tough defender unit attackPower: 10, // Good damage for a defender attackRange: 100, // Slightly better melee range speed: 1.5, // Same speed as melee units assetId: 'humanGuardian', attackSoundId: 'attackMelee' }, // --- Researched Player Units --- skeletonRookie: { hp: 70, attackPower: 8, attackRange: 90, speed: 1.5, // Match militia speed to prevent getting faster when upgraded assetId: 'skeletonRookie', // Use the dedicated rookie asset cost: 20, attackSoundId: 'attackMelee' }, // Reusing militia asset for now skeletonTraineeArcher: { hp: 40, attackPower: 10, attackRange: 450, speed: 1.2, // Match slinger speed to prevent getting faster when upgraded assetId: 'skeletonTraineearcher', // Use the dedicated trainee archer asset cost: 25, attackCooldownTime: 70, attackSoundId: 'attackRanged' }, // --- Tier 2 Researched Player Units --- skeletonSwordsman: { hp: 100, // Stronger than Rookie attackPower: 12, // Higher damage attackRange: 100, // Slightly better reach speed: 1.5, // Match militia/rookie speed to prevent getting faster when upgraded assetId: 'skeletonSwordsman', cost: 35, // Cost after research attackSoundId: 'attackMelee' }, skeletonArcher: { hp: 55, // More durable than Trainee attackPower: 14, // Significantly more damage attackRange: 500, // Longer range speed: 1.2, // Match slinger/trainee speed to prevent getting faster when upgraded assetId: 'skeletonArcher', cost: 40, // Cost after research attackCooldownTime: 60, // Faster attack speed attackSoundId: 'attackRanged' }, // --- Tier 1 & 2 Defender Player Units --- skeletonShielded: { // Tier 1 Defender hp: 120, // High health attackPower: 4, // Low damage attackRange: 80, // Melee range speed: 1.5, // Match militia/rookie speed assetId: 'skeletonShielded', cost: 25, // Cost after research attackSoundId: 'attackMelee' }, skeletonHoplite: { // Tier 2 Defender hp: 180, // Very high health attackPower: 7, // Slightly better damage attackRange: 90, // Melee range speed: 1.5, // Match militia/rookie/swordsman speed assetId: 'skeletonHolpite', cost: 45, // Cost after research attackSoundId: 'attackMelee' }, // --- Tier 3 Researched Player Units --- skeletonEliteswordsman: { hp: 150, // Stronger than Swordsman attackPower: 18, // Significantly higher damage attackRange: 110, // Slightly better reach speed: 1.5, // Match previous melee speed assetId: 'skeletonEliteswordsman', cost: 60, // Cost after research attackSoundId: 'attackMelee' }, skeletonCrossbowman: { hp: 75, // More durable than Archer attackPower: 20, // High damage attackRange: 550, // Longest range speed: 1.2, // Match previous ranged speed assetId: 'skeletonCrossbowman', cost: 70, // Cost after research attackCooldownTime: 50, // Even faster attack speed attackSoundId: 'attackRanged' }, skeletonDarkKnight: { hp: 250, // Extremely high health attackPower: 12, // Better damage than Hoplite attackRange: 100, // Melee range speed: 1.5, // Match previous melee speed assetId: 'skeletonDarkKnight', cost: 80, // Cost after research attackSoundId: 'attackMelee' }, // --- Tier 1-3 Artillery Player Units --- skeletonBomber: { // Tier 1 Artillery hp: 20, // Very fragile attackPower: 15, // High damage attackRange: 600, // Long range speed: 0.8, // Very slow assetId: 'skeletonBomber', cost: 30, // Cost after research attackCooldownTime: 120, // Slow attack speed attackSoundId: 'attackRanged' // Placeholder, maybe a 'boom' sound later }, skeletonPumpkinCatapult: { // Tier 2 Artillery hp: 40, // Still fragile attackPower: 25, // Very high damage attackRange: 700, // Longer range speed: 0.7, // Even slower assetId: 'skeletonPumpkinCatapult', cost: 55, // Cost after research attackCooldownTime: 150, // Slower attack speed attackSoundId: 'attackRanged' // Placeholder }, skeletonDragon: { // Tier 3 Artillery hp: 80, // Moderately durable for artillery attackPower: 40, // Extremely high damage attackRange: 800, // Very long range speed: 1.0, // Slightly faster than others assetId: 'skeletonDragon', cost: 100, // Cost after research attackCooldownTime: 180, // Very slow attack speed attackSoundId: 'attackRanged' // Placeholder } }; // Research configuration var SHIELDED_RESEARCH_COST = 60; var HOPLITE_RESEARCH_COST = 180; // Research state var shieldedResearchComplete = false; // Tier 1 Defender var hopliteResearchComplete = false; // Tier 2 Defender // Tier 3 Research Costs var ELITESWORDSMAN_RESEARCH_COST = 250; var CROSSBOWMAN_RESEARCH_COST = 300; var DARKKNIGHT_RESEARCH_COST = 350; // Tier 3 Research State var eliteSwordsmanResearchComplete = false; var crossbowmanResearchComplete = false; var darkKnightResearchComplete = false; // Artillery Research Costs var BOMBER_RESEARCH_COST = 80; var CATAPULT_RESEARCH_COST = 200; var DRAGON_RESEARCH_COST = 400; // Artillery Research State var bomberResearchComplete = false; var catapultResearchComplete = false; var dragonResearchComplete = false; // Game state variables - Defined globally for access within functions and update loop var gold = 10; var playerUnits = []; var enemyUnits = []; var isBattling = false; var battleCounter = 0; // Tracks the number of battles won var currentEnemyConfig = {}; // Store the config for the current battle var goldenPiggyInstance = null; // Holds the active Golden Piggy instance // Initial player army composition (can be modified by recruitment) // Using an object for easier modification by type var playerArmyRoster = { skeletonMilitia: 2, skeletonSlinger: 1 }; // --- UI Elements --- // Gold Display var goldTxt = new Text2('Gold: 0', { size: 60, fill: 0xFFD700 }); goldTxt.anchor.set(1.0, 0); // Anchor top-right goldTxt.x = -50; // Offset from the right edge goldTxt.y = 20; // Offset from the top edge // Avoid top-left (reserved for LK menu), top-right is fine. LK.gui.topRight.addChild(goldTxt); // Village Counter Display var villageTxt = new Text2('Village: 1', { size: 60, fill: 0xFFD700 }); villageTxt.anchor.set(0.0, 0); // Anchor top-left villageTxt.x = 50; // Offset from the left edge villageTxt.y = 20; // Offset from the top edge LK.gui.topLeft.addChild(villageTxt); // Buttons Container (for organizing buttons at the bottom) var buttonContainer = new Container(); buttonContainer.x = GAME_WIDTH / 2; buttonContainer.y = GAME_HEIGHT - 200; // Position buttons lower game.addChild(buttonContainer); // Add to the main game scene // Start Battle Button var startButton = new Button('Start Battle', { width: 400, height: 120, textSize: 50, bgColor: 0x008000 }); // Greenish button startButton.y = -80; // Position above the recruitment buttons buttonContainer.addChild(startButton); // Recruitment Buttons (Example: Militia) var recruitMilitiaButton = new Button('Recruit Militia (10g)', { width: 400, height: 100, textSize: 40, bgColor: 0x404040 }); recruitMilitiaButton.x = -330; // Position further left for three buttons recruitMilitiaButton.y = 80; // Position below start button buttonContainer.addChild(recruitMilitiaButton); // Example: Slinger recruitment button var recruitSlingerButton = new Button('Recruit Slinger (15g)', { width: 400, height: 100, textSize: 40, bgColor: 0x404040 }); recruitSlingerButton.x = 330; // Position further right for three buttons recruitSlingerButton.y = 80; // Position below start button buttonContainer.addChild(recruitSlingerButton); // Defender recruitment button var recruitDefenderButton = new Button('Recruit Defender (25g)', { width: 400, height: 100, textSize: 40, bgColor: 0x404040 }); recruitDefenderButton.x = 0; // Center position recruitDefenderButton.y = 80; // Same row as other recruitment buttons buttonContainer.addChild(recruitDefenderButton); // Research Buttons var researchMilitiaButton = new Button('Research Rookie (50g)', { width: 400, height: 100, textSize: 40, bgColor: 0x004080 // Blueish button }); researchMilitiaButton.x = -220; // Position left, above recruitment researchMilitiaButton.y = -220; // Position above recruitment buttons buttonContainer.addChild(researchMilitiaButton); var researchSlingerButton = new Button('Research Trainee (75g)', { width: 400, height: 100, textSize: 40, bgColor: 0x004080 // Blueish button }); researchSlingerButton.x = 220; // Position right, above recruitment researchSlingerButton.y = -220; // Position above recruitment buttons buttonContainer.addChild(researchSlingerButton); // Tier 2 Research Buttons (Initially hidden) var researchSwordsmanButton = new Button('Research Swordsman (' + SWORDSMAN_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, // Slightly smaller text if needed bgColor: 0x800080 // Purpleish button }); researchSwordsmanButton.x = -220; researchSwordsmanButton.y = -350; // Position above Tier 1 research researchSwordsmanButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchSwordsmanButton); var researchArcherButton = new Button('Research Archer (' + ARCHER_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, // Slightly smaller text if needed bgColor: 0x800080 // Purpleish button }); researchArcherButton.x = 220; researchArcherButton.y = -350; // Position above Tier 1 research researchArcherButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchArcherButton); buttonContainer.addChild(researchArcherButton); // Defender Research Buttons var researchShieldedButton = new Button('Research Shielded (' + SHIELDED_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, bgColor: 0x6A5ACD // SlateBlue button }); researchShieldedButton.x = 0; // Position center, above Tier 1 Melee/Ranged researchShieldedButton.y = -220; // Same level as Tier 1 Melee/Ranged research buttonContainer.addChild(researchShieldedButton); var researchHopliteButton = new Button('Research Hoplite (' + HOPLITE_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, bgColor: 0x483D8B // DarkSlateBlue button }); researchHopliteButton.x = 0; // Position center, above Tier 2 Melee/Ranged researchHopliteButton.y = -350; // Same level as Tier 2 Melee/Ranged research researchHopliteButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchHopliteButton); // Adjust positions to accommodate the third column (Defender research) // Tier 1 Research researchMilitiaButton.x = -440; // Move left researchMilitiaButton.y = -220; researchSlingerButton.x = 440; // Move right researchSlingerButton.y = -220; researchShieldedButton.x = 0; // Center researchShieldedButton.y = -220; // Tier 2 Research researchSwordsmanButton.x = -440; // Move left researchSwordsmanButton.y = -350; researchArcherButton.x = 440; // Move right researchArcherButton.y = -350; researchHopliteButton.x = 0; // Center researchHopliteButton.y = -350; // Adjust Start Button position further up startButton.y = -480; // Move start button further up // Tier 3 Research Buttons (Initially hidden) var researchEliteSwordsmanButton = new Button('Research Elite (' + ELITESWORDSMAN_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, bgColor: 0xFF4500 // Orangish-Red button }); researchEliteSwordsmanButton.x = -440; // Position left, above Tier 2 researchEliteSwordsmanButton.y = -480; // Position above Tier 2 research researchEliteSwordsmanButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchEliteSwordsmanButton); var researchCrossbowmanButton = new Button('Research Crossbow (' + CROSSBOWMAN_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, bgColor: 0xFF4500 // Orangish-Red button }); researchCrossbowmanButton.x = 440; // Position right, above Tier 2 researchCrossbowmanButton.y = -480; // Position above Tier 2 research researchCrossbowmanButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchCrossbowmanButton); var researchDarkKnightButton = new Button('Research D.Knight (' + DARKKNIGHT_RESEARCH_COST + 'g)', { width: 400, height: 100, textSize: 35, bgColor: 0x8B0000 // DarkRed button }); researchDarkKnightButton.x = 0; // Position center, above Tier 2 researchDarkKnightButton.y = -480; // Position above Tier 2 research researchDarkKnightButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchDarkKnightButton); // Adjust Start Button position even further up to accommodate Tier 3 research startButton.y = -610; // Move start button further up // --- Artillery Research Buttons --- var researchBomberButton = new Button('Research Bomber (' + BOMBER_RESEARCH_COST + 'g)', { width: 380, // Slightly narrower for 4 columns height: 100, textSize: 35, bgColor: 0x8A2BE2 // BlueViolet }); researchBomberButton.x = 220; // Position between Defender and Ranged (Right side) researchBomberButton.y = -220; // Tier 1 row researchBomberButton.visible = true; // Initially visible buttonContainer.addChild(researchBomberButton); var researchCatapultButton = new Button('Research Catapult (' + CATAPULT_RESEARCH_COST + 'g)', { width: 380, height: 100, textSize: 35, bgColor: 0x9932CC // DarkOrchid }); researchCatapultButton.x = 220; // Position between Defender and Ranged (Right side) researchCatapultButton.y = -350; // Tier 2 row researchCatapultButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchCatapultButton); var researchDragonButton = new Button('Research Dragon (' + DRAGON_RESEARCH_COST + 'g)', { width: 380, height: 100, textSize: 35, bgColor: 0xBA55D3 // MediumOrchid }); researchDragonButton.x = 220; // Position between Defender and Ranged (Right side) researchDragonButton.y = -480; // Tier 3 row researchDragonButton.visible = false; // Hidden until prerequisite met buttonContainer.addChild(researchDragonButton); // --- Adjust existing button positions for 4 columns --- var buttonXOffsetLarge = 660; // For outer buttons (Melee, Ranged) var buttonXOffsetSmall = 220; // For inner buttons (Defender, Artillery) // Research Buttons researchMilitiaButton.x = -buttonXOffsetLarge; // Far Left researchMilitiaButton.width = 380; researchShieldedButton.x = -buttonXOffsetSmall; // Mid Left // Fix: Used correct variable name researchShieldedButton.width = 380; // Fix: Used correct variable name researchBomberButton.x = buttonXOffsetSmall; // Mid Right (new) researchBomberButton.width = 380; researchSlingerButton.x = buttonXOffsetLarge; // Far Right researchSlingerButton.width = 380; researchSwordsmanButton.x = -buttonXOffsetLarge; // Far Left researchSwordsmanButton.width = 380; researchHopliteButton.x = -buttonXOffsetSmall; // Mid Left researchHopliteButton.width = 380; researchCatapultButton.x = buttonXOffsetSmall; // Mid Right (new) researchCatapultButton.width = 380; researchArcherButton.x = buttonXOffsetLarge; // Far Right researchArcherButton.width = 380; researchEliteSwordsmanButton.x = -buttonXOffsetLarge; // Far Left researchEliteSwordsmanButton.width = 380; researchDarkKnightButton.x = -buttonXOffsetSmall; // Mid Left researchDarkKnightButton.width = 380; researchDragonButton.x = buttonXOffsetSmall; // Mid Right (new) researchDragonButton.width = 380; researchCrossbowmanButton.x = buttonXOffsetLarge; // Far Right researchCrossbowmanButton.width = 380; // Recruitment Buttons recruitMilitiaButton.x = -buttonXOffsetLarge; // Far Left recruitMilitiaButton.width = 380; recruitDefenderButton.x = -buttonXOffsetSmall; // Mid Left recruitDefenderButton.width = 380; // recruitArtilleryButton needs to be created first, then positioned recruitSlingerButton.x = buttonXOffsetLarge; // Far Right recruitSlingerButton.width = 380; // Adjust Start Button position further up startButton.y = -610; // Keep this adjustment // --- Add Artillery Recruitment Button --- var recruitArtilleryButton = new Button('Research Required', { // Initial text width: 380, // Match other buttons height: 100, textSize: 35, // Match research buttons bgColor: 0x404040 }); recruitArtilleryButton.x = buttonXOffsetSmall; // Position Mid Right recruitArtilleryButton.y = 80; // Same row as other recruitment buttons recruitArtilleryButton.setEnabled(false); // Initially disabled buttonContainer.addChild(recruitArtilleryButton); // --- Game Logic Functions --- function updateGoldDisplay() { goldTxt.setText('Gold: ' + gold); var canAffordMilitiaResearch = gold >= MILITIA_RESEARCH_COST; var canAffordSlingerResearch = gold >= SLINGER_RESEARCH_COST; var canAffordShieldedResearch = gold >= SHIELDED_RESEARCH_COST; // New Defender cost check var canAffordSwordsmanResearch = gold >= SWORDSMAN_RESEARCH_COST; var canAffordArcherResearch = gold >= ARCHER_RESEARCH_COST; var canAffordHopliteResearch = gold >= HOPLITE_RESEARCH_COST; var canAffordEliteSwordsmanResearch = gold >= ELITESWORDSMAN_RESEARCH_COST; var canAffordCrossbowmanResearch = gold >= CROSSBOWMAN_RESEARCH_COST; var canAffordDarkKnightResearch = gold >= DARKKNIGHT_RESEARCH_COST; // Artillery research cost checks var canAffordBomberResearch = gold >= BOMBER_RESEARCH_COST; var canAffordCatapultResearch = gold >= CATAPULT_RESEARCH_COST; var canAffordDragonResearch = gold >= DRAGON_RESEARCH_COST; // --- Update Research Buttons --- // Tier 1: Militia -> Rookie if (militiaResearchComplete) { researchMilitiaButton.visible = false; // Hide Tier 1 when complete researchMilitiaButton.setEnabled(false); } else { researchMilitiaButton.visible = true; researchMilitiaButton.setEnabled(canAffordMilitiaResearch && !isBattling); } // Tier 1: Slinger -> Trainee if (slingerResearchComplete) { researchSlingerButton.visible = false; // Hide Tier 1 when complete researchSlingerButton.setEnabled(false); } else { researchSlingerButton.visible = true; researchSlingerButton.setEnabled(canAffordSlingerResearch && !isBattling); } // Tier 1: Defender -> Shielded Skeleton if (shieldedResearchComplete) { researchShieldedButton.visible = false; researchShieldedButton.setEnabled(false); } else { researchShieldedButton.visible = true; researchShieldedButton.setEnabled(canAffordShieldedResearch && !isBattling); } // Tier 2: Rookie -> Swordsman if (swordsmanResearchComplete) { researchSwordsmanButton.visible = false; // Hide Tier 2 when complete researchSwordsmanButton.setEnabled(false); } else { // Show Tier 2 only if Tier 1 is complete researchSwordsmanButton.visible = militiaResearchComplete; researchSwordsmanButton.setEnabled(militiaResearchComplete && canAffordSwordsmanResearch && !isBattling); } // Tier 2: Trainee -> Archer if (archerResearchComplete) { researchArcherButton.visible = false; // Hide Tier 2 when complete researchArcherButton.setEnabled(false); } else { // Show Tier 2 only if Tier 1 is complete researchArcherButton.visible = slingerResearchComplete; researchArcherButton.setEnabled(slingerResearchComplete && canAffordArcherResearch && !isBattling); } // Tier 2: Shielded -> Hoplite if (hopliteResearchComplete) { researchHopliteButton.visible = false; researchHopliteButton.setEnabled(false); } else { // Show Tier 2 only if Tier 1 is complete researchHopliteButton.visible = shieldedResearchComplete; researchHopliteButton.setEnabled(shieldedResearchComplete && canAffordHopliteResearch && !isBattling); } // Tier 3: Swordsman -> Elite Swordsman if (eliteSwordsmanResearchComplete) { researchEliteSwordsmanButton.visible = false; researchEliteSwordsmanButton.setEnabled(false); } else { // Show Tier 3 only if Tier 2 is complete researchEliteSwordsmanButton.visible = swordsmanResearchComplete; researchEliteSwordsmanButton.setEnabled(swordsmanResearchComplete && canAffordEliteSwordsmanResearch && !isBattling); } // Tier 3: Archer -> Crossbowman if (crossbowmanResearchComplete) { researchCrossbowmanButton.visible = false; researchCrossbowmanButton.setEnabled(false); } else { // Show Tier 3 only if Tier 2 is complete researchCrossbowmanButton.visible = archerResearchComplete; researchCrossbowmanButton.setEnabled(archerResearchComplete && canAffordCrossbowmanResearch && !isBattling); } // Tier 3: Hoplite -> Dark Knight if (darkKnightResearchComplete) { researchDarkKnightButton.visible = false; researchDarkKnightButton.setEnabled(false); } else { // Show Tier 3 only if Tier 2 is complete researchDarkKnightButton.visible = hopliteResearchComplete; researchDarkKnightButton.setEnabled(hopliteResearchComplete && canAffordDarkKnightResearch && !isBattling); } // Tier 1: Artillery -> Bomber if (bomberResearchComplete) { researchBomberButton.visible = false; researchBomberButton.setEnabled(false); } else { researchBomberButton.visible = true; researchBomberButton.setEnabled(canAffordBomberResearch && !isBattling); } // Tier 2: Bomber -> Catapult if (catapultResearchComplete) { researchCatapultButton.visible = false; researchCatapultButton.setEnabled(false); } else { // Show Tier 2 only if Tier 1 is complete researchCatapultButton.visible = bomberResearchComplete; researchCatapultButton.setEnabled(bomberResearchComplete && canAffordCatapultResearch && !isBattling); } // Tier 3: Catapult -> Dragon if (dragonResearchComplete) { researchDragonButton.visible = false; researchDragonButton.setEnabled(false); } else { // Show Tier 3 only if Tier 2 is complete researchDragonButton.visible = catapultResearchComplete; researchDragonButton.setEnabled(catapultResearchComplete && canAffordDragonResearch && !isBattling); } // --- Update Recruitment Buttons --- // Determine current highest researched MELEE unit var currentMilitiaType = 'skeletonMilitia'; var militiaButtonText = 'Recruit Militia'; if (eliteSwordsmanResearchComplete) { currentMilitiaType = 'skeletonEliteswordsman'; militiaButtonText = 'Recruit Elite'; } else if (swordsmanResearchComplete) { currentMilitiaType = 'skeletonSwordsman'; militiaButtonText = 'Recruit Swordsman'; } else if (militiaResearchComplete) { currentMilitiaType = 'skeletonRookie'; militiaButtonText = 'Recruit Rookie'; } var currentMilitiaStats = UNIT_STATS[currentMilitiaType]; recruitMilitiaButton.setText(militiaButtonText + ' (' + currentMilitiaStats.cost + 'g)'); recruitMilitiaButton.setEnabled(gold >= currentMilitiaStats.cost && !isBattling); // Determine current highest researched RANGED unit var currentSlingerType = 'skeletonSlinger'; var slingerButtonText = 'Recruit Slinger'; if (crossbowmanResearchComplete) { currentSlingerType = 'skeletonCrossbowman'; slingerButtonText = 'Recruit Crossbow'; } else if (archerResearchComplete) { currentSlingerType = 'skeletonArcher'; slingerButtonText = 'Recruit Archer'; } else if (slingerResearchComplete) { currentSlingerType = 'skeletonTraineeArcher'; slingerButtonText = 'Recruit Trainee'; } var currentSlingerStats = UNIT_STATS[currentSlingerType]; recruitSlingerButton.setText(slingerButtonText + ' (' + currentSlingerStats.cost + 'g)'); recruitSlingerButton.setEnabled(gold >= currentSlingerStats.cost && !isBattling); // Determine current highest researched DEFENDER unit var currentDefenderType = null; var defenderButtonText = 'Research Required'; if (darkKnightResearchComplete) { currentDefenderType = 'skeletonDarkKnight'; defenderButtonText = 'Recruit D.Knight'; } else if (hopliteResearchComplete) { currentDefenderType = 'skeletonHoplite'; defenderButtonText = 'Recruit Hoplite'; } else if (shieldedResearchComplete) { currentDefenderType = 'skeletonShielded'; defenderButtonText = 'Recruit Shielded'; } // Update defender button state based on research status if (currentDefenderType) { var currentDefenderStats = UNIT_STATS[currentDefenderType]; recruitDefenderButton.setText(defenderButtonText + ' (' + currentDefenderStats.cost + 'g)'); recruitDefenderButton.setEnabled(gold >= currentDefenderStats.cost && !isBattling); } else { // No defender research completed yet recruitDefenderButton.setText('Research Required'); recruitDefenderButton.setEnabled(false); } // Determine current highest researched ARTILLERY unit var currentArtilleryType = null; var artilleryButtonText = 'Research Required'; if (dragonResearchComplete) { currentArtilleryType = 'skeletonDragon'; artilleryButtonText = 'Recruit Dragon'; } else if (catapultResearchComplete) { currentArtilleryType = 'skeletonPumpkinCatapult'; artilleryButtonText = 'Recruit Catapult'; } else if (bomberResearchComplete) { currentArtilleryType = 'skeletonBomber'; artilleryButtonText = 'Recruit Bomber'; } // Update artillery button state based on research status if (currentArtilleryType) { var currentArtilleryStats = UNIT_STATS[currentArtilleryType]; recruitArtilleryButton.setText(artilleryButtonText + ' (' + currentArtilleryStats.cost + 'g)'); recruitArtilleryButton.setEnabled(gold >= currentArtilleryStats.cost && !isBattling); } else { // No artillery research completed yet recruitArtilleryButton.setText('Research Required'); recruitArtilleryButton.setEnabled(false); // Ensure it's disabled } } function recruitUnit(unitType) { if (isBattling) { return; } // Cannot recruit during battle var stats = UNIT_STATS[unitType]; if (!stats) { console.error("Attempted to recruit unknown unit type:", unitType); return; } if (gold >= stats.cost) { gold -= stats.cost; try { // Defensive coding for sound playback LK.getSound('recruit').play(); } catch (e) { console.log("Sound error playing recruit: " + e); } // Safely increment the count in the roster playerArmyRoster[unitType] = (playerArmyRoster[unitType] || 0) + 1; console.log("Recruited " + unitType + ". Roster:", playerArmyRoster); updateGoldDisplay(); // Update UI after recruitment } else { console.log("Not enough gold to recruit " + unitType); // Optional: Show feedback to the player (e.g., flashing the gold red) LK.effects.flashObject(goldTxt, 0xFF0000, 300); } } function researchUnit(researchType) { if (isBattling) { return; } // Cannot research during battle var cost = 0; var cost = 0; var researchName = ''; var canResearch = false; var setResearchFlag = null; // Function to set the correct flag if (researchType === 'militia' && !militiaResearchComplete) { cost = MILITIA_RESEARCH_COST; researchName = 'Rookie'; canResearch = true; // No prerequisite setResearchFlag = function setResearchFlag() { militiaResearchComplete = true; }; } else if (researchType === 'slinger' && !slingerResearchComplete) { cost = SLINGER_RESEARCH_COST; researchName = 'Trainee Archer'; canResearch = true; // No prerequisite setResearchFlag = function setResearchFlag() { slingerResearchComplete = true; }; } else if (researchType === 'swordsman' && !swordsmanResearchComplete) { cost = SWORDSMAN_RESEARCH_COST; researchName = 'Swordsman'; canResearch = militiaResearchComplete; // Prerequisite: Rookie researched setResearchFlag = function setResearchFlag() { swordsmanResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Rookie first."); } } else if (researchType === 'archer' && !archerResearchComplete) { cost = ARCHER_RESEARCH_COST; researchName = 'Archer'; canResearch = slingerResearchComplete; // Prerequisite: Trainee researched setResearchFlag = function setResearchFlag() { archerResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Trainee Archer first."); } } else if (researchType === 'shielded' && !shieldedResearchComplete) { // New Defender Tier 1 cost = SHIELDED_RESEARCH_COST; researchName = 'Shielded Skeleton'; canResearch = true; // No prerequisite setResearchFlag = function setResearchFlag() { shieldedResearchComplete = true; }; } else if (researchType === 'hoplite' && !hopliteResearchComplete) { // New Defender Tier 2 cost = HOPLITE_RESEARCH_COST; researchName = 'Skeleton Hoplite'; canResearch = shieldedResearchComplete; // Prerequisite: Shielded researched setResearchFlag = function setResearchFlag() { hopliteResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Shielded Skeleton first."); } } else if (researchType === 'eliteswordsman' && !eliteSwordsmanResearchComplete) { // Tier 3 Melee cost = ELITESWORDSMAN_RESEARCH_COST; researchName = 'Elite Swordsman'; canResearch = swordsmanResearchComplete; // Prerequisite: Swordsman researched setResearchFlag = function setResearchFlag() { eliteSwordsmanResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Swordsman first."); } } else if (researchType === 'crossbowman' && !crossbowmanResearchComplete) { // Tier 3 Ranged cost = CROSSBOWMAN_RESEARCH_COST; researchName = 'Crossbowman'; canResearch = archerResearchComplete; // Prerequisite: Archer researched setResearchFlag = function setResearchFlag() { crossbowmanResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Archer first."); } } else if (researchType === 'darkknight' && !darkKnightResearchComplete) { // Tier 3 Defender cost = DARKKNIGHT_RESEARCH_COST; researchName = 'Dark Knight'; canResearch = hopliteResearchComplete; // Prerequisite: Hoplite researched setResearchFlag = function setResearchFlag() { darkKnightResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Hoplite first."); } } else if (researchType === 'bomber' && !bomberResearchComplete) { // Artillery Tier 1 cost = BOMBER_RESEARCH_COST; researchName = 'Skeleton Bomber'; canResearch = true; // No prerequisite setResearchFlag = function setResearchFlag() { bomberResearchComplete = true; }; } else if (researchType === 'catapult' && !catapultResearchComplete) { // Artillery Tier 2 cost = CATAPULT_RESEARCH_COST; researchName = 'Pumpkin Catapult'; canResearch = bomberResearchComplete; // Prerequisite: Bomber setResearchFlag = function setResearchFlag() { catapultResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Bomber first."); } } else if (researchType === 'dragon' && !dragonResearchComplete) { // Artillery Tier 3 cost = DRAGON_RESEARCH_COST; researchName = 'Skeleton Dragon'; canResearch = catapultResearchComplete; // Prerequisite: Catapult setResearchFlag = function setResearchFlag() { dragonResearchComplete = true; }; if (!canResearch) { console.log("Prerequisite not met: Research Catapult first."); } } else { console.log("Research already complete, prerequisite not met, or invalid type:", researchType); return; // Invalid type, already researched, or prereq not met } if (canResearch && gold >= cost) { gold -= cost; setResearchFlag(); // Set the specific research flag to true try { // Reuse upgrade sound, or create a specific 'research' sound LK.getSound('upgradeUnit').play(); // Assuming 'upgradeUnit' sound is suitable } catch (e) { console.log("Sound error playing research sound: " + e); } console.log("Research complete for:", researchName); updateGoldDisplay(); // Update UI immediately } else { console.log("Not enough gold to research " + researchName); LK.effects.flashObject(goldTxt, 0xFF0000, 300); } } function clearBattlefield() { // Remove all existing units from the game and arrays for (var i = playerUnits.length - 1; i >= 0; i--) { if (playerUnits[i] && playerUnits[i].destroy) { // Check if destroy exists playerUnits[i].destroy(); } } playerUnits = []; // Reset array for (var i = enemyUnits.length - 1; i >= 0; i--) { if (enemyUnits[i] && enemyUnits[i].destroy) { // Check if destroy exists enemyUnits[i].destroy(); } } enemyUnits = []; // Reset array // Remove the golden piggy if it exists if (goldenPiggyInstance !== null) { goldenPiggyInstance.destroy(); goldenPiggyInstance = null; } } function spawnUnit(unitType, isEnemy) { var stats = UNIT_STATS[unitType]; if (!stats) { console.error("Cannot spawn unknown unit type:", unitType); return null; } // Prepare config for the Unit class constructor var config = { isEnemy: isEnemy, hp: stats.hp, attackPower: stats.attackPower, attackRange: stats.attackRange, speed: stats.speed, assetId: stats.assetId, attackCooldownTime: stats.attackCooldownTime, attackSoundId: stats.attackSoundId, unitType: unitType // Pass the unit type }; // Create new instance of Unit var unit = new Unit(config); // Determine spawn side and array var armyArray = isEnemy ? enemyUnits : playerUnits; var spawnX = isEnemy ? ENEMY_SPAWN_X : PLAYER_SPAWN_X; // Calculate staggered Y position var currentY = SPAWN_Y_START + armyArray.length * SPAWN_Y_STEP; // Wrap Y position if it goes beyond the designated spawn area end if (currentY > SPAWN_Y_END) { var yRange = SPAWN_Y_END - SPAWN_Y_START; if (yRange <= 0) { yRange = SPAWN_Y_STEP; } // Avoid division by zero or negative range currentY = SPAWN_Y_START + armyArray.length * SPAWN_Y_STEP % yRange; } // Set initial position and state unit.x = spawnX; unit.y = currentY; unit.lastX = unit.x; // Initialize last state correctly // Add the unit to the game display and the logical array game.addChild(unit); armyArray.push(unit); return unit; // Return the created unit if needed elsewhere } function startBattle() { if (isBattling) { return; } // Prevent starting multiple battles console.log("Starting battle..."); isBattling = true; clearBattlefield(); // Remove any units from a previous battle // Define enemy forces for this battle (can be made dynamic later) if (Object.keys(currentEnemyConfig).length === 0) { // Only initialize on first battle currentEnemyConfig = { humanFarmer: 3, humanSlinger: 1 }; } // Otherwise use the already incremented config from previous win // Spawn Player Units based on the current roster console.log("Spawning player units:", playerArmyRoster); for (var unitType in playerArmyRoster) { if (playerArmyRoster.hasOwnProperty(unitType)) { // Good practice for iterating objects var count = playerArmyRoster[unitType]; for (var i = 0; i < count; i++) { spawnUnit(unitType, false); // Spawn player unit (not enemy) } } } // Spawn Enemy Units based on the current configuration console.log("Spawning enemy units:", currentEnemyConfig); for (var unitType in currentEnemyConfig) { if (currentEnemyConfig.hasOwnProperty(unitType)) { var count = currentEnemyConfig[unitType]; for (var i = 0; i < count; i++) { spawnUnit(unitType, true); // Spawn enemy unit } } } // Hide UI elements not relevant during battle buttonContainer.visible = false; // Ensure units arrays are populated before the first update runs console.log("Battle started. Player units:", playerUnits.length, "Enemy units:", enemyUnits.length); } function endBattle(playerWon) { // Check if the battle hasn't already ended to prevent multiple triggers if (!isBattling) { return; } console.log("Battle ended. Player won:", playerWon); isBattling = false; // Set state before doing anything else // Explicitly remove piggy when battle ends, BEFORE showing UI again. // This ensures it's properly cleaned up and allows a new one to spawn in subsequent battles. if (goldenPiggyInstance !== null) { goldenPiggyInstance.destroy(); goldenPiggyInstance = null; } // Show UI elements again buttonContainer.visible = true; if (playerWon) { var goldEarned = 50; // Example gold reward gold += goldEarned; try { // Defensive coding for sound playback LK.getSound('battleWon').play(); } catch (e) { console.log("Sound error playing battleWon: " + e); } console.log("Earned " + goldEarned + " gold. Total gold:", gold); // Update display immediately after awarding gold updateGoldDisplay(); battleCounter++; // Increment the battle counter on win villageTxt.setText('Village: ' + (battleCounter + 1)); // Update the village counter display console.log("Battle won! Proceeding to village: " + (battleCounter + 1)); // Humans gain more units when player wins battles for (var unitType in currentEnemyConfig) { if (currentEnemyConfig.hasOwnProperty(unitType)) { // Increase each existing unit type by 1-2 for the next battle currentEnemyConfig[unitType] += Math.floor(Math.random() * 2) + 1; } } // Introduce new units starting from the 5th battle (after winning the 4th) if (battleCounter >= 4) { // 4 wins means the next battle is the 5th // Add Guard if not present, or increase count if (!currentEnemyConfig.hasOwnProperty('humanGuard')) { currentEnemyConfig['humanGuard'] = 1; // Start with 1 } else { currentEnemyConfig['humanGuard'] += 1; // Add one more each subsequent win } // Add Militia Archer if not present, or increase count if (!currentEnemyConfig.hasOwnProperty('humanMilitiaArcher')) { currentEnemyConfig['humanMilitiaArcher'] = 1; // Start with 1 } else { currentEnemyConfig['humanMilitiaArcher'] += 1; // Add one more each subsequent win } console.log("Stronger human reinforcements (Guards, Militia Archers) have arrived!"); } // Introduce Local Protector starting from the 6th battle (after winning the 5th) if (battleCounter >= 5) { // 5 wins means the next battle is the 6th if (!currentEnemyConfig.hasOwnProperty('humanLocalprotector')) { currentEnemyConfig['humanLocalprotector'] = 1; // Start with 1 } else { currentEnemyConfig['humanLocalprotector'] += 1; // Add one more each win } console.log("Local Protector reinforcements have arrived!"); } // Introduce elite units starting from the 8th battle (after winning the 7th) if (battleCounter >= 7) { // 7 wins means the next battle is the 8th // Add Elite Guard if not present, or increase count if (!currentEnemyConfig.hasOwnProperty('humanEliteGuard')) { currentEnemyConfig['humanEliteGuard'] = 1; // Start with 1 } else { currentEnemyConfig['humanEliteGuard'] += 1; // Add one more each subsequent win } // Add Archer if not present, or increase count if (!currentEnemyConfig.hasOwnProperty('humanArcher')) { currentEnemyConfig['humanArcher'] = 1; // Start with 1 } else { currentEnemyConfig['humanArcher'] += 1; // Add one more each subsequent win } console.log("Elite human reinforcements (Elite Guards, Archers) have arrived!"); } // Introduce Guardian starting from the 9th battle (after winning the 8th) if (battleCounter >= 8) { // 8 wins means the next battle is the 9th if (!currentEnemyConfig.hasOwnProperty('humanGuardian')) { currentEnemyConfig['humanGuardian'] = 1; // Start with 1 } else { currentEnemyConfig['humanGuardian'] += 1; // Add one more each win } console.log("Guardian reinforcements have arrived!"); } // Introduce Bomber starting from the 13th battle (after winning the 12th) if (battleCounter >= 12) { if (!currentEnemyConfig.hasOwnProperty('Bomber')) { currentEnemyConfig['Bomber'] = 1; } else { currentEnemyConfig['Bomber'] += 1; } console.log("Bomber reinforcements have arrived!"); } // Introduce King's Guard starting from the 15th battle (after winning the 14th) if (battleCounter >= 14) { if (!currentEnemyConfig.hasOwnProperty("King's guard")) { currentEnemyConfig["King's guard"] = 1; } else { currentEnemyConfig["King's guard"] += 1; } console.log("King's Guard reinforcements have arrived!"); } // Introduce Crossbowman starting from the 16th battle (after winning the 15th) if (battleCounter >= 15) { if (!currentEnemyConfig.hasOwnProperty("Crossbowman")) { currentEnemyConfig["Crossbowman"] = 1; } else { currentEnemyConfig["Crossbowman"] += 1; } console.log("Crossbowman reinforcements have arrived!"); } // Introduce Cannoneer starting from the 20th battle (after winning the 19th) if (battleCounter >= 19) { if (!currentEnemyConfig.hasOwnProperty('Cannoneer')) { currentEnemyConfig['Cannoneer'] = 1; } else { currentEnemyConfig['Cannoneer'] += 1; } console.log("Cannoneer reinforcements have arrived!"); } // Introduce King's Shield starting from the 23rd battle (after winning the 22nd) if (battleCounter >= 22) { if (!currentEnemyConfig.hasOwnProperty("King's shield")) { currentEnemyConfig["King's shield"] = 1; } else { currentEnemyConfig["King's shield"] += 1; } console.log("King's Shield reinforcements have arrived!"); } // Introduce Trebuchet starting from the 26th battle (after winning the 25th) if (battleCounter >= 25) { if (!currentEnemyConfig.hasOwnProperty('Trebuchet')) { currentEnemyConfig['Trebuchet'] = 1; } else { currentEnemyConfig['Trebuchet'] += 1; } console.log("Trebuchet reinforcements have arrived!"); } console.log("Enemy forces have grown stronger for the next battle:", currentEnemyConfig); // Game continues, player can choose to recruit or battle again } else { console.log("Player lost the battle."); // Game over is handled by LK engine which resets the game state LK.showGameOver(); // Note: Gold and roster will reset unless storage plugin is used. } // Update button states based on new gold amount AFTER battle outcome is decided // This is especially important if the player lost, as LK.showGameOver might reset things // before this line runs if not careful. updateGoldDisplay() called inside win condition is safer. // If player loses, the game resets anyway, so updating buttons might not be needed here. if (playerWon) { updateGoldDisplay(); // Ensure buttons reflect new gold total after winning } } // --- Initialize Game State --- // Assign onClick handlers to buttons *after* they are created startButton.onClick = startBattle; // Assign the function reference recruitMilitiaButton.onClick = function () { var unitToRecruit = 'skeletonMilitia'; // Default if (eliteSwordsmanResearchComplete) { unitToRecruit = 'skeletonEliteswordsman'; } else if (swordsmanResearchComplete) { unitToRecruit = 'skeletonSwordsman'; } else if (militiaResearchComplete) { unitToRecruit = 'skeletonRookie'; } recruitUnit(unitToRecruit); }; recruitSlingerButton.onClick = function () { var unitToRecruit = 'skeletonSlinger'; // Default if (crossbowmanResearchComplete) { unitToRecruit = 'skeletonCrossbowman'; } else if (archerResearchComplete) { unitToRecruit = 'skeletonArcher'; } else if (slingerResearchComplete) { unitToRecruit = 'skeletonTraineeArcher'; } recruitUnit(unitToRecruit); }; recruitDefenderButton.onClick = function () { // Default to no unit (research required) var unitToRecruit = null; // Check research status to determine which defender unit to recruit if (darkKnightResearchComplete) { unitToRecruit = 'skeletonDarkKnight'; } else if (hopliteResearchComplete) { unitToRecruit = 'skeletonHoplite'; } else if (shieldedResearchComplete) { unitToRecruit = 'skeletonShielded'; } // Only attempt to recruit if a valid unit type is available if (unitToRecruit) { recruitUnit(unitToRecruit); } }; // Tier 1 Research Button Handlers researchMilitiaButton.onClick = function () { researchUnit('militia'); }; researchSlingerButton.onClick = function () { researchUnit('slinger'); }; // Tier 2 Research Button Handlers researchSwordsmanButton.onClick = function () { researchUnit('swordsman'); }; researchArcherButton.onClick = function () { researchUnit('archer'); }; // Defender Research Button Handlers researchShieldedButton.onClick = function () { researchUnit('shielded'); }; researchHopliteButton.onClick = function () { researchUnit('hoplite'); }; // Tier 3 Research Button Handlers researchEliteSwordsmanButton.onClick = function () { researchUnit('eliteswordsman'); }; researchCrossbowmanButton.onClick = function () { researchUnit('crossbowman'); }; researchDarkKnightButton.onClick = function () { researchUnit('darkknight'); }; // Artillery Research Button Handlers researchBomberButton.onClick = function () { researchUnit('bomber'); }; researchCatapultButton.onClick = function () { researchUnit('catapult'); }; researchDragonButton.onClick = function () { researchUnit('dragon'); }; // Artillery Recruitment Button Handler recruitArtilleryButton.onClick = function () { var unitToRecruit = null; if (dragonResearchComplete) { unitToRecruit = 'skeletonDragon'; } else if (catapultResearchComplete) { unitToRecruit = 'skeletonPumpkinCatapult'; } else if (bomberResearchComplete) { unitToRecruit = 'skeletonBomber'; } // Only attempt to recruit if a valid unit type is available if (unitToRecruit) { recruitUnit(unitToRecruit); } }; updateGoldDisplay(); // Set initial gold text and button states based on starting gold (10) and research state // --- Game Update Loop --- // This function is called by the LK engine automatically 60 times per second. game.update = function () { if (!isBattling) { // Game is idle, waiting for player action (recruit/start battle) // No unit updates needed. return; } // --- Battle Logic --- // Update Player Units (Iterate backwards for safe removal) for (var i = playerUnits.length - 1; i >= 0; i--) { var pUnit = playerUnits[i]; // Ensure unit exists and has methods before calling them if (!pUnit) { continue; } if (pUnit.isDead) { if (pUnit.destroy) { pUnit.destroy(); } // Remove graphics from game playerUnits.splice(i, 1); // Remove from logical array continue; // Move to the next unit } // Pass the array of enemies for target finding if (pUnit.update) { pUnit.update(enemyUnits); } } // Update Enemy Units (Iterate backwards for safe removal) for (var i = enemyUnits.length - 1; i >= 0; i--) { var eUnit = enemyUnits[i]; if (!eUnit) { continue; } if (eUnit.isDead) { if (eUnit.destroy) { eUnit.destroy(); } enemyUnits.splice(i, 1); continue; } // Pass the array of player units for target finding if (eUnit.update) { eUnit.update(playerUnits); } } // --- Golden Piggy Logic --- // Chance to spawn a piggy if one isn't already active if (isBattling && goldenPiggyInstance === null) { // Low chance per tick, e.g., 1 in 1000 ticks on average var PIGGY_SPAWN_CHANCE = 1 / (60 * 10); // Roughly once every 10 seconds average if (Math.random() < PIGGY_SPAWN_CHANCE) { console.log("Spawning Golden Piggy!"); goldenPiggyInstance = new GoldenPiggy(); // Spawn position (e.g., random y in the middle vertical third, center horizontally) var spawnY = GAME_HEIGHT * 0.33 + Math.random() * (GAME_HEIGHT * 0.33); goldenPiggyInstance.x = GAME_WIDTH / 2; goldenPiggyInstance.y = spawnY; game.addChild(goldenPiggyInstance); // Add to display } } // Update active piggy and remove if collected/timed out if (goldenPiggyInstance !== null) { if (goldenPiggyInstance.isCollected) { goldenPiggyInstance.destroy(); goldenPiggyInstance = null; } else { goldenPiggyInstance.update(); // Call piggy's own update logic } } // Check Win/Loss Conditions *after* all units have been updated for the tick // Only check if the battle is currently marked as active if (isBattling) { if (enemyUnits.length === 0 && playerUnits.length > 0) { // Player wins: All enemies are defeated, player still has units. endBattle(true); } else if (playerUnits.length === 0) { // Player loses: All player units are defeated. (Enemy count doesn't matter here) // We might get here even if enemies also died in the same tick. endBattle(false); } } }; // --- Input Handling --- // Button clicks are managed by the Button class instances via their `up` method. // No global game-level mouse/touch handlers needed for this MVP's interaction model.
===================================================================
--- original.js
+++ change.js
@@ -249,8 +249,56 @@
self.isDead = false;
self.lastX = 0; // Initialize lastX, will be set correctly on spawn
// Upgrade logic removed - replaced by global research system
});
+// WebGL Shader initialization
+var WebGLHelper = Container.expand(function () {
+ var self = Container.call(this);
+ // Method to initialize WebGL shaders
+ self.initShaders = function (renderer) {
+ if (!renderer || !renderer.gl) {
+ return;
+ }
+ try {
+ var createShader = function createShader(gl, source, type) {
+ var shader = gl.createShader(type);
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ console.error('WebGL shader compilation failed: ' + gl.getShaderInfoLog(shader));
+ gl.deleteShader(shader);
+ return null;
+ }
+ return shader;
+ };
+ var gl = renderer.gl;
+ // Vertex shader
+ var vertexShaderSource = "\n\t\t\t\tattribute vec2 aVertexPosition;\n\t\t\t\tattribute vec2 aTextureCoord;\n\t\t\t\tuniform mat3 projectionMatrix;\n\t\t\t\tvarying vec2 vTextureCoord;\n\t\t\t\tvoid main(void) {\n\t\t\t\t\tgl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n\t\t\t\t\tvTextureCoord = aTextureCoord;\n\t\t\t\t}";
+ // Fragment shader
+ var fragmentShaderSource = "\n\t\t\t\tprecision mediump float;\n\t\t\t\tvarying vec2 vTextureCoord;\n\t\t\t\tuniform sampler2D uSampler;\n\t\t\t\tvoid main(void) {\n\t\t\t\t\tgl_FragColor = texture2D(uSampler, vTextureCoord);\n\t\t\t\t}";
+ // Create shader program
+ var vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
+ var fragmentShader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
+ if (!vertexShader || !fragmentShader) {
+ return;
+ }
+ // Create program
+ var program = gl.createProgram();
+ gl.attachShader(program, vertexShader);
+ gl.attachShader(program, fragmentShader);
+ gl.linkProgram(program);
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+ console.error('WebGL program linking failed: ' + gl.getProgramInfoLog(program));
+ gl.deleteProgram(program);
+ return;
+ }
+ return program;
+ } catch (e) {
+ console.error('WebGL shader initialization error:', e);
+ }
+ };
+ return self;
+});
/****
* Initialize Game
****/
@@ -260,21 +308,21 @@
/****
* Game Code
****/
-// Ensure melee sound exists
+// Button background
+// Blue ellipse
+// Green box
+// Gray ellipse
+// White box
+// Define logical assets for units and UI.
+// LK Engine automatically creates assets based on usage.
+// Game constants and configuration
+// Added assets for new human units
// Placeholder ID
// Placeholder ID
// Placeholder ID
-// Added assets for new human units
-// Game constants and configuration
-// LK Engine automatically creates assets based on usage.
-// Define logical assets for units and UI.
-// White box
-// Gray ellipse
-// Green box
-// Blue ellipse
-// Button background
+// Ensure melee sound exists
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_SPAWN_X = 150;
var ENEMY_SPAWN_X = GAME_WIDTH - 150;
Skeleton with a wooden sword. In-Game asset. 2d. High contrast. No shadows
Skeleton facing left with a slingshot. In-Game asset. 2d. High contrast. No shadows
Farmer with a pitchfork. In-Game asset. 2d. High contrast. No shadows
Farmer with a slingshot. In-Game asset. 2d. High contrast. No shadows
Skeleton with copper sword, copper helmet and copper shield. In-Game asset. 2d. High contrast. No shadows
A skeleton with a bow. In-Game asset. 2d. High contrast. No shadows
Skeleton with Iron sword, iron helmet and a Reinforced wooden shield. In-Game asset. 2d. High contrast. No shadows
Skeleton with a reinforced bow and a hood. In-Game asset. 2d. High contrast. No shadows
Human with copper Armor, a copper sword and shield. In-Game asset. 2d. High contrast. No shadows
A human with a bow and a quiver. In-Game asset. 2d. High contrast. No shadows
Human with Iron Armor, iron sword and iron shield. In-Game asset. 2d. High contrast. No shadows
A human with a reinforced bow, iron helmet and a bucket full of arrows. In-Game asset. 2d. High contrast. No shadows
Skeleton holding a big copper shield with both hands. In-Game asset. 2d. High contrast. No shadows
Skeleton with an Iron helmet, Iron spear and a big iron shield. In-Game asset. 2d. High contrast. No shadows
A golden pig. In-Game asset. 2d. High contrast. No shadows
Human with a big iron shield. In-Game asset. 2d. High contrast. No shadows
Human with a big Copper shield. In-Game asset. 2d. High contrast. No shadows
Skeleton with a blue hoodie and cape, golden sword and a reinforced iron shield. In-Game asset. 2d. High contrast. No shadows
Skeleton with a dead wolf's head as a hood, a Crossbow and a big quiver. In-Game asset. 2d. High contrast. No shadows
Skeleton with it's skull covered with a dark helmet, a dark axe and a shield with a red skull in the middle. In-Game asset. 2d. High contrast. No shadows
Skeleton with a bomb. In-Game asset. 2d. High contrast. No shadows
Pumpkin catapult made out of bones. In-Game asset. 2d. High contrast. No shadows
Skeleton with a blue hoodie and cape riding a skeleton dragon. In-Game asset. 2d. High contrast. No shadows
A knight with golden armour, golden shield and golden sword. In-Game asset. 2d. High contrast. No shadows
A human with a golden helmet, crossbow and a big bucket full of arrows in his back. In-Game asset. 2d. High contrast. No shadows
Human with a BIG golden shield and shining reinforced golden armor. In-Game asset. 2d. High contrast. No shadows
Human with a bomb. In-Game asset. 2d. High contrast. No shadows
Cannon. In-Game asset. 2d. High contrast. No shadows
Trebuchet on wheels. In-Game asset. 2d. High contrast. No shadows