diff --git a/Script.js b/Script.js index 9c40e1d..651d94f 100644 --- a/Script.js +++ b/Script.js @@ -41,6 +41,7 @@ let currentWave = null; let waveCooldown = 0; let abilityCharges = 0; +let missileAmmo = 0; // Missile Ammo var game = { level: 1, @@ -92,7 +93,8 @@ for (var i = 0; i < enemyImgArray.length; i++) { enemyImgArray[i].src = "img/alien_" + [i] + ".png"; } -var missilesArray = []; +var missilesArray = []; // Laser Biasa +var playerMissilesArray = []; // Homing Missile var enemyShipArray = []; var enemyBulletsArray = []; var explosions = []; @@ -163,26 +165,36 @@ function gameLoop(timestamp) { let gamePaused = false; function keyDownPressed(e) { - if (e.keyCode === 87 || e.keyCode === 38) keys.up = true; - else if (e.keyCode === 83 || e.keyCode === 40) keys.down = true; - if (e.keyCode === 65 || e.keyCode === 37) keys.left = true; - if (e.keyCode === 68 || e.keyCode === 39) keys.right = true; + if (e.keyCode === 87 || e.keyCode === 38) keys.up = true; // W / UP + else if (e.keyCode === 83 || e.keyCode === 40) keys.down = true; // S / DOWN + if (e.keyCode === 65 || e.keyCode === 37) keys.left = true; // A / LEFT + if (e.keyCode === 68 || e.keyCode === 39) keys.right = true; // D / RIGHT if (e.keyCode === 32) { + // SPACE keys.fire = true; if (!player1.dead) { fireBullet(); } } - if (e.keyCode === 80) togglePause(); + if (e.keyCode === 80) togglePause(); // P if (e.keyCode === 16) { + // SHIFT (Bomb) if (abilityCharges > 0 && !game.gameOver && !gamePaused && !player1.dead) { useAbility(); abilityCharges--; } } + + // --- TOMBOL Q UNTUK MISSILE --- + if (e.keyCode === 81) { + // Q + if (!game.gameOver && !gamePaused && !player1.dead) { + firePlayerMissile(); + } + } } function keyUpPressed(e) { @@ -193,18 +205,15 @@ function keyUpPressed(e) { if (e.keyCode === 32) keys.fire = false; } -// --- UPDATE FIRE BULLET: Cek Skill Double Laser --- +// --- NORMAL LASER --- function fireBullet() { if (player1.doubleLaserTimer > 0) { - // --- SKILL AKTIF: TEMBAK 2 PELURU --- - // Atas missilesArray.push( new LaserBullet( player1.x + player1.width, player1.y + player1.height / 2 - 15 ) ); - // Bawah missilesArray.push( new LaserBullet( player1.x + player1.width, @@ -212,7 +221,6 @@ function fireBullet() { ) ); } else { - // --- NORMAL: TEMBAK 1 PELURU --- missilesArray.push( new LaserBullet(player1.x + player1.width, player1.y + player1.height / 2) ); @@ -229,6 +237,25 @@ function fireBullet() { ); } +// --- FIRE HOMING MISSILE (Q) --- +function firePlayerMissile() { + if (missileAmmo > 0) { + missileAmmo--; + playerMissilesArray.push( + new PlayerMissile( + player1.x + player1.width, + player1.y + player1.height / 2 - 10 + ) + ); + + // Sound effect + let sfx = explosion_enemy.cloneNode(); + sfx.volume = 0.5; + sfx.playbackRate = 2.0; + sfx.play(); + } +} + function clearGame() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); } @@ -299,16 +326,18 @@ function drawGame() { height: t.height, }) ) { - // Cek Tipe Orb if (t.type === "bomb") { - // --- ORB KUNING (Bomb) --- + // KUNING = BOMB abilityCharges++; - createParticles(t.x, t.y, 15, "#ffff00"); // Partikel Kuning + createParticles(t.x, t.y, 15, "#ffff00"); } else if (t.type === "double") { - // --- ORB MERAH (Double Laser) --- - // 10 Detik = 60 FPS * 10 = 600 Frames + // MERAH = DOUBLE LASER player1.doubleLaserTimer = 600; - createParticles(t.x, t.y, 15, "#ff0000"); // Partikel Merah + createParticles(t.x, t.y, 15, "#ff0000"); + } else if (t.type === "missile") { + // BIRU TUA = MISSILE (+3 Ammo) + missileAmmo += 3; + createParticles(t.x, t.y, 15, "#0000ff"); } abilityTokens.splice(i, 1); @@ -340,7 +369,6 @@ function drawGame() { continue; } - // BALANCING TEMBAKAN (Low Rate) let shootChance = 0.005 + game.level * 0.0012; if (shootChance > 0.04) shootChance = 0.04; @@ -362,6 +390,7 @@ function drawGame() { } } + // --- UPDATE LASER BIASA --- for (let i = 0; i < missilesArray.length; i++) { let m = missilesArray[i]; m.draw(); @@ -374,7 +403,6 @@ function drawGame() { let en = enemyShipArray[j]; if (Tabrakan(m.getHitbox(), en.getHitbox())) { - // --- BALANCING DAMAGE PLAYER --- let playerDamage = 100 + game.level * 5; en.health -= playerDamage; @@ -411,6 +439,53 @@ function drawGame() { } } + // --- UPDATE PLAYER MISSILES (AUTO LOCK) --- + for (let i = 0; i < playerMissilesArray.length; i++) { + let pm = playerMissilesArray[i]; + pm.draw(); + pm.update(); + + let hit = false; + for (let j = 0; j < enemyShipArray.length; j++) { + let en = enemyShipArray[j]; + + if (Tabrakan(pm.getHitbox(), en.getHitbox())) { + // DAMAGE MISSILE: 400 + (Level * 20) + let missileDmg = 400 + game.level * 20; + en.health -= missileDmg; + + // Efek ledakan besar biru + createParticles(pm.x + pm.width, pm.y, 20, "#0000ff"); + explosions.push(new Explosion(pm.x + pm.width, pm.y, 0.5)); + + playerMissilesArray.splice(i, 1); + hit = true; + + if (en.health <= 0) { + player1.score += 100 + game.level * 10; + explosion_enemy.currentTime = 0; + explosion_enemy.play(); + explosions.push( + new Explosion(en.x + en.width / 2, en.y + en.height / 2) + ); + enemyShipArray.splice(j, 1); + } + break; + } + } + + if (hit) { + i--; + continue; + } + + // Hapus jika keluar layar jauh + if (pm.x > canvasWidth + 200 || pm.y < -200 || pm.y > canvasHeight + 200) { + playerMissilesArray.splice(i, 1); + i--; + } + } + for (let i = 0; i < enemyBulletsArray.length; i++) { let b = enemyBulletsArray[i]; b.draw(); @@ -486,10 +561,10 @@ function drawUI() { livesText += "♥ "; } drawNewText(livesText, 30, canvasHeight - 50, "#ff3366"); - drawNewText("Bombs: " + abilityCharges, 30, 50, "#ffffff"); - // --- (HAPUS) INDIKATOR TIMER DOUBLE LASER --- - // Teks tidak lagi ditampilkan, tapi durasi skill tetap berjalan di update() + // UI SKILL + drawNewText("Bombs (Shift): " + abilityCharges, 30, 50, "#ffff00"); + drawNewText("Missiles (Q): " + missileAmmo, 30, 85, "#00ccff"); // Updated Text } class PlayerObject { @@ -512,14 +587,12 @@ class PlayerObject { this.invincible = 0; this.dead = false; - // --- SKILL TIMER --- - this.doubleLaserTimer = 0; // Timer dalam frame + this.doubleLaserTimer = 0; this.totalFrames = 5; this.frameIndex = 2; this.spriteWidth = 0; this.sourceHeight = 0; - // --- SIZE PLAYER: Middle Ground --- this.scale = 1.0; this.image.onload = () => { @@ -620,7 +693,6 @@ class PlayerObject { this.frameIndex = 2; } - // --- KURANGI TIMER SKILL --- if (this.doubleLaserTimer > 0) { this.doubleLaserTimer--; } @@ -749,11 +821,98 @@ class LaserBullet { } } +// --- NEW CLASS: HOMING PLAYER MISSILE --- +class PlayerMissile { + constructor(x, y) { + this.x = x; + this.y = y; + this.width = 30; + this.height = 12; + this.speed = 2; // Kecepatan Awal + this.maxSpeed = 18; + this.vx = 2; + this.vy = 0; + this.target = null; // Target yang akan dikejar + } + + getHitbox() { + return { x: this.x, y: this.y, width: this.width, height: this.height }; + } + + draw() { + ctx.save(); + // Gambar body missile (Biru) + let g = ctx.createLinearGradient( + this.x, + this.y, + this.x + this.width, + this.y + ); + g.addColorStop(0, "#00008b"); + g.addColorStop(0.5, "#4169e1"); + g.addColorStop(1, "#ffffff"); + + ctx.fillStyle = g; + ctx.beginPath(); + ctx.moveTo(this.x, this.y); + ctx.lineTo(this.x + this.width, this.y + this.height / 2); + ctx.lineTo(this.x, this.y + this.height); + ctx.fill(); + + // Trail Effect + if (Math.random() < 0.5) { + createParticles(this.x, this.y + this.height / 2, 2, "#00bfff"); + } + ctx.restore(); + } + + update() { + // 1. Akselerasi (makin lama makin cepat) + this.speed *= 1.08; + if (this.speed > this.maxSpeed) this.speed = this.maxSpeed; + + // 2. Logic Cari Musuh Terdekat (Auto Lock) + if (!this.target || !enemyShipArray.includes(this.target)) { + let minDist = 100000; + let closest = null; + for (let e of enemyShipArray) { + let dx = e.x - this.x; + let dy = e.y - this.y; + let d = Math.sqrt(dx * dx + dy * dy); + if (d < minDist) { + minDist = d; + closest = e; + } + } + this.target = closest; + } + + // 3. Gerak Mengejar Target + if (this.target) { + let tx = this.target.x + this.target.width / 2; + let ty = this.target.y + this.target.height / 2; + let dx = tx - this.x; + let dy = ty - this.y; + let angle = Math.atan2(dy, dx); + + // Update velocity ke arah musuh + this.vx = Math.cos(angle) * this.speed; + this.vy = Math.sin(angle) * this.speed; + } else { + // Jika tidak ada target, terbang lurus + this.vx = this.speed; + this.vy = 0; + } + + this.x += this.vx; + this.y += this.vy; + } +} + class EnemyObj { constructor(x, y, speed, img, pattern = "straight") { this.x = x; this.y = y; - // --- SIZE MUSUH: Middle Ground --- this.width = 145; this.height = 90; this.image = img; @@ -967,6 +1126,12 @@ class AbilityToken { this.speed = 4; // Random Type saat spawn: "bomb" atau "double" this.type = Math.random() < 0.5 ? "bomb" : "double"; + + // RAND: 0-0.33=Bomb, 0.33-0.66=Double, 0.66-1.0=Missile + let r = Math.random(); + if (r < 0.33) this.type = "bomb"; + else if (r < 0.66) this.type = "double"; + else this.type = "missile"; } draw() { @@ -984,10 +1149,14 @@ class AbilityToken { // Kuning (Bomb) g.addColorStop(0.5, "#ffff00"); g.addColorStop(1, "#ff9900"); - } else { + } else if (this.type === "double") { // Merah (Double Laser) g.addColorStop(0.5, "#ff3333"); g.addColorStop(1, "#990000"); + } else { + // Biru Tua (Missile) + g.addColorStop(0.5, "#0000ff"); + g.addColorStop(1, "#00008b"); } ctx.fillStyle = g; @@ -1002,7 +1171,7 @@ class AbilityToken { function maybeSpawnAbilityToken() { // --- KEMBALI KE 0.2% (0.002) --- - if (Math.random() < 0.002 && abilityTokens.length < 3) { + if (Math.random() < 0.003 && abilityTokens.length < 3) { const y = Math.random() * (canvasHeight - 120) + 60; abilityTokens.push(new AbilityToken(canvasWidth + 40, y)); }