diff --git a/package.json b/package.json index c0830e6..8450c32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "whalehunting-game", - "version": "1.1.0", + "version": "1.2.0", "description": "A Monkey Island-style point-and-click adventure game about 18th century whaling", "main": "src/main.js", "scripts": { diff --git a/src/scenes/DeepSeaHuntingScene.js b/src/scenes/DeepSeaHuntingScene.js index 5fc098b..b31c4c5 100644 --- a/src/scenes/DeepSeaHuntingScene.js +++ b/src/scenes/DeepSeaHuntingScene.js @@ -11,6 +11,9 @@ export default class DeepSeaHuntingScene extends Phaser.Scene { this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 }; this.whalesHunted = 0; this.currentWhale = null; + this.harpoons = []; + this.crosshairX = 400; + this.crosshairY = 300; } create() { @@ -27,11 +30,248 @@ export default class DeepSeaHuntingScene extends Phaser.Scene { // Fullscreen button createFullscreenButton(this); + // Create crosshair + this.createCrosshair(); + + // Setup input + this.setupInput(); + // Spawn first whale this.spawnWhale(); + // Detect mobile + this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + // Show initial message - this.showMessage('The leviathan approaches... Watch the depths!'); + const shootMessage = this.isMobile ? 'The leviathan approaches... Tap to harpoon!' : 'The leviathan approaches... Click to harpoon!'; + this.showMessage(shootMessage); + } + + update() { + // Update crosshair position + this.crosshairX = this.input.x; + this.crosshairY = this.input.y; + this.crosshair.setPosition(this.crosshairX, this.crosshairY); + + // Update harpoons + this.updateHarpoons(); + + // Check collisions + this.checkCollisions(); + } + + createCrosshair() { + const graphics = this.add.graphics(); + graphics.lineStyle(3, 0x4a9fff, 1); + + // Crosshair lines + graphics.beginPath(); + graphics.moveTo(-20, 0); + graphics.lineTo(-5, 0); + graphics.moveTo(5, 0); + graphics.lineTo(20, 0); + graphics.moveTo(0, -20); + graphics.lineTo(0, -5); + graphics.moveTo(0, 5); + graphics.lineTo(0, 20); + graphics.strokePath(); + + // Center circle + graphics.lineStyle(2, 0x4a9fff, 1); + graphics.strokeCircle(0, 0, 3); + + // Convert to texture and create sprite + graphics.generateTexture('crosshair_deep', 40, 40); + graphics.destroy(); + + this.crosshair = this.add.sprite(400, 300, 'crosshair_deep'); + this.crosshair.setDepth(100); + } + + setupInput() { + // Mouse/touch click to shoot + this.input.on('pointerdown', () => { + this.shootHarpoon(); + }); + + // Space to shoot + this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); + this.spaceKey.on('down', () => { + this.shootHarpoon(); + }); + + // Hide cursor on desktop + if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + this.input.setDefaultCursor('none'); + } + } + + shootHarpoon() { + // Create harpoon at crosshair position + const harpoon = this.add.container(this.crosshairX, this.crosshairY); + + // Harpoon shaft with glow effect + const glow = this.add.rectangle(0, 0, 8, 30, 0x4a9fff, 0.3); + const shaft = this.add.rectangle(0, 0, 4, 25, 0x6abfff); + const tip = this.add.triangle(0, -15, 0, -8, -5, 0, 5, 0, 0x4a9fff); + + harpoon.add([glow, shaft, tip]); + harpoon.setData('active', true); + harpoon.setDepth(50); + + this.harpoons.push(harpoon); + + // Flash effect + const flash = this.add.circle(this.crosshairX, this.crosshairY, 15, 0x4a9fff, 0.6); + this.tweens.add({ + targets: flash, + alpha: 0, + scale: 2, + duration: 200, + onComplete: () => flash.destroy() + }); + } + + updateHarpoons() { + for (let i = this.harpoons.length - 1; i >= 0; i--) { + const harpoon = this.harpoons[i]; + + if (!harpoon.getData('active')) { + continue; + } + + // Move harpoon up + harpoon.y -= 10; + + // Remove if off screen + if (harpoon.y < -30) { + harpoon.destroy(); + this.harpoons.splice(i, 1); + } + } + } + + checkCollisions() { + if (!this.currentWhale || !this.currentWhale.getData('alive')) { + return; + } + + const whale = this.currentWhale; + + for (let i = this.harpoons.length - 1; i >= 0; i--) { + const harpoon = this.harpoons[i]; + + if (!harpoon.getData('active')) { + continue; + } + + const distance = Phaser.Math.Distance.Between( + harpoon.x, harpoon.y, + whale.x, whale.y + ); + + if (distance < 90) { + this.hitWhale(whale, harpoon, i); + break; + } + } + } + + hitWhale(whale, harpoon, harpoonIndex) { + harpoon.setData('active', false); + harpoon.destroy(); + this.harpoons.splice(harpoonIndex, 1); + + // Reduce health + let health = whale.getData('health'); + health -= 1; + whale.setData('health', health); + + // Update health bar + const healthBar = whale.getData('healthBar'); + const maxHealth = whale.getData('maxHealth'); + const healthPercent = health / maxHealth; + healthBar.setDisplaySize(120 * healthPercent, 10); + + // Change color based on health + if (healthPercent > 0.6) { + healthBar.setFillStyle(0x00ff00); + } else if (healthPercent > 0.3) { + healthBar.setFillStyle(0xffff00); + } else { + healthBar.setFillStyle(0xff0000); + } + + // Hit flash + const hitFlash = this.add.circle(whale.x, whale.y, 60, 0x4a9fff, 0.5); + this.tweens.add({ + targets: hitFlash, + alpha: 0, + scale: 1.5, + duration: 300, + onComplete: () => hitFlash.destroy() + }); + + if (health <= 0) { + this.killWhale(whale); + } else { + this.showMessage(`Hit! Leviathan health: ${health}/${maxHealth}`); + } + } + + killWhale(whale) { + whale.setData('alive', false); + + // Stop the path tween + this.tweens.killTweensOf(whale.getData('pathData')); + + // Check fuel + if (this.inventory.fuel < 3) { + this.tweens.add({ + targets: whale, + alpha: 0, + y: whale.y + 100, + duration: 1500, + onComplete: () => { + whale.destroy(); + this.currentWhale = null; + this.time.delayedCall(2000, () => this.spawnWhale()); + } + }); + this.showMessage('Leviathan slain but no fuel to process! Need 3 fuel.'); + return; + } + + // Death animation + this.tweens.add({ + targets: whale, + alpha: 0, + y: whale.y + 100, + angle: whale.rotation * (180 / Math.PI) + 90, + duration: 1500, + onComplete: () => { + whale.destroy(); + this.currentWhale = null; + this.time.delayedCall(2000, () => this.spawnWhale()); + } + }); + + // Rewards - leviathans give more oil + this.inventory.fuel -= 3; + this.inventory.whaleOil += 3; + this.whalesHunted++; + this.updateStats(); + + this.showMessage(`Leviathan slain! +3 Oil, -3 Fuel`); + + // Success flash + const flash = this.add.rectangle(400, 300, 800, 600, 0x4a9fff, 0.3); + this.tweens.add({ + targets: flash, + alpha: 0, + duration: 500, + onComplete: () => flash.destroy() + }); } createDeepOceanAtmosphere() { @@ -124,10 +364,22 @@ export default class DeepSeaHuntingScene extends Phaser.Scene { ease: 'Sine.inOut' }); - whale.add([tail, body, belly, dorsalFin, eyeGlow, eye]); + // Health bar background + const healthBg = this.add.rectangle(0, -55, 120, 10, 0x000000); + healthBg.setStrokeStyle(1, 0x4a9fff); + // Health bar + const healthBar = this.add.rectangle(0, -55, 120, 10, 0x00ff00); + + whale.add([tail, body, belly, dorsalFin, eyeGlow, eye, healthBg, healthBar]); whale.setRotation(angle); whale.setDepth(10); + // Set whale data + whale.setData('alive', true); + whale.setData('health', 5); // Leviathans are tougher - 5 hits + whale.setData('maxHealth', 5); + whale.setData('healthBar', healthBar); + this.currentWhale = whale; // Subtle bobbing animation during movement @@ -170,6 +422,7 @@ export default class DeepSeaHuntingScene extends Phaser.Scene { // Store path progress const pathData = { t: 0 }; + whale.setData('pathData', pathData); // Animate along curved path this.tweens.add({ @@ -279,6 +532,7 @@ export default class DeepSeaHuntingScene extends Phaser.Scene { } returnToMap() { + this.input.setDefaultCursor('default'); this.scene.start('MapScene', { inventory: this.inventory }); } } diff --git a/src/scenes/IntroScene.js b/src/scenes/IntroScene.js index bcbddf5..d7fdb16 100644 --- a/src/scenes/IntroScene.js +++ b/src/scenes/IntroScene.js @@ -84,7 +84,7 @@ export default class IntroScene extends Phaser.Scene { createFullscreenButton(this); // Version display - this.add.text(790, 590, 'v1.1.0', { + this.add.text(790, 590, 'v1.2.0', { fontSize: fontSize(12), fill: '#ffffff', alpha: 0.5