927 lines
31 KiB
JavaScript
927 lines
31 KiB
JavaScript
// Player Entity
|
|
// Igralec z WASD kontrolami in isometrično podporo
|
|
class Player {
|
|
constructor(scene, gridX = 50, gridY = 50, offsetX = 0, offsetY = 0) {
|
|
this.scene = scene;
|
|
this.gridX = gridX;
|
|
this.gridY = gridY;
|
|
|
|
// Terrain offset
|
|
this.offsetX = offsetX;
|
|
this.offsetY = offsetY;
|
|
|
|
this.tileSize = (this.scene.terrainSystem && this.scene.terrainSystem.tileSize) || 48;
|
|
this.iso = new IsometricUtils(this.tileSize, this.tileSize / 2);
|
|
|
|
// 🎮 SMOOTH MOVEMENT SYSTEM (Hybrid)
|
|
this.moveSpeed = 100; // Normal speed px/s
|
|
this.sprintSpeed = 200; // Sprint speed px/s
|
|
this.acceleration = 0.15; // How fast to reach target speed
|
|
|
|
// Velocity (current movement)
|
|
this.velocity = { x: 0, y: 0 };
|
|
// Target velocity (where we want to go)
|
|
this.targetVelocity = { x: 0, y: 0 };
|
|
|
|
// Sprint system
|
|
this.sprinting = false;
|
|
this.energy = 100;
|
|
this.maxEnergy = 100;
|
|
this.energyDrain = 10; // per second while sprinting
|
|
|
|
// State
|
|
// State
|
|
this.isMoving = false;
|
|
this.isActing = false; // 🛑 Stardew Action Lock
|
|
|
|
this.direction = 'down';
|
|
this.lastDir = { x: 0, y: 1 }; // Default south
|
|
|
|
// Stats
|
|
this.hp = 100;
|
|
this.maxHp = 100;
|
|
this.isDead = false;
|
|
|
|
// Kreira sprite
|
|
this.createSprite();
|
|
|
|
// Setup kontrole
|
|
this.setupControls();
|
|
|
|
// Space za napad
|
|
this.scene.input.keyboard.on('keydown-SPACE', () => {
|
|
if (!this.isDead) this.attack();
|
|
});
|
|
|
|
// Začetna pozicija
|
|
this.updatePosition();
|
|
}
|
|
|
|
takeDamage(amount) {
|
|
if (this.isDead) return;
|
|
|
|
// GOD MODE - Invincibility
|
|
if (window.godMode) {
|
|
console.log('⚡ GOD MODE: Damage blocked!');
|
|
return;
|
|
}
|
|
|
|
this.hp -= amount;
|
|
console.log(`Player HP: ${this.hp}`);
|
|
|
|
// Visual Feedback
|
|
this.scene.cameras.main.shake(100, 0.01);
|
|
this.sprite.setTint(0xff0000);
|
|
this.scene.time.delayedCall(200, () => {
|
|
if (!this.isDead) this.sprite.clearTint();
|
|
});
|
|
|
|
// Update UI (če obstaja StatsSystem ali UI)
|
|
if (this.scene.statsSystem) {
|
|
// Scene specific stats update if linked
|
|
}
|
|
|
|
if (this.hp <= 0) {
|
|
this.die();
|
|
}
|
|
}
|
|
|
|
die() {
|
|
this.isDead = true;
|
|
this.sprite.setTint(0x555555);
|
|
this.sprite.setRotation(Math.PI / 2); // Lie down
|
|
console.log('💀 PLAYER DIED');
|
|
|
|
// Death Sound
|
|
if (this.scene.soundManager) {
|
|
this.scene.soundManager.playDeath();
|
|
}
|
|
|
|
// Show Game Over / Reload
|
|
const txt = this.scene.add.text(this.scene.cameras.main.midPoint.x, this.scene.cameras.main.midPoint.y, 'YOU DIED', {
|
|
fontSize: '64px', color: '#ff0000', fontStyle: 'bold', stroke: '#000', strokeThickness: 6
|
|
}).setOrigin(0.5).setDepth(20000);
|
|
|
|
this.scene.time.delayedCall(3000, () => {
|
|
window.location.reload();
|
|
});
|
|
}
|
|
|
|
createSprite() {
|
|
// Priority: kai_main (real sprite) > player_style32 > fallback
|
|
let texKey = 'kai_main';
|
|
|
|
if (!this.scene.textures.exists(texKey)) {
|
|
texKey = 'player_style32';
|
|
}
|
|
|
|
// Fallback to generated pink square if nothing loaded
|
|
if (!this.scene.textures.exists(texKey)) {
|
|
console.warn('⚠️ No Kai sprite found! Generating pink fallback...');
|
|
TextureGenerator.createPlayerSprite(this.scene, 'player_fallback');
|
|
texKey = 'player_fallback';
|
|
}
|
|
|
|
// Kreira sprite
|
|
// 🎨 FLAT 2D (NEW!) - Direct screen conversion
|
|
const screenPos = {
|
|
x: Math.round((this.gridX * this.tileSize) + (this.tileSize / 2)),
|
|
y: Math.round((this.gridY * this.tileSize) + (this.tileSize / 2))
|
|
};
|
|
this.sprite = this.scene.add.sprite(
|
|
screenPos.x + this.offsetX,
|
|
screenPos.y + this.offsetY,
|
|
texKey,
|
|
0 // CRITICAL: Start with frame 0 (first frame only)
|
|
);
|
|
this.sprite.setOrigin(0.5, 1.0); // Bottom center - new sprite has full character!
|
|
|
|
// Scale based on sprite type
|
|
if (texKey === 'player_protagonist') {
|
|
this.sprite.setScale(0.25); // 0.25 scale for correct size
|
|
} else {
|
|
this.sprite.setScale(0.5); // Fallback sprite
|
|
}
|
|
|
|
|
|
// --- HAND / HELD ITEM SPRITE ---
|
|
this.handSprite = this.scene.add.sprite(
|
|
screenPos.x + this.offsetX + 10,
|
|
screenPos.y + this.offsetY - 25,
|
|
'item_axe'
|
|
);
|
|
this.handSprite.setOrigin(0.5, 0.5);
|
|
this.handSprite.setScale(0.25);
|
|
this.handSprite.setVisible(false);
|
|
|
|
this.updateDepth();
|
|
}
|
|
|
|
setupControls() {
|
|
this.keys = this.scene.input.keyboard.addKeys({
|
|
// WASD
|
|
w: Phaser.Input.Keyboard.KeyCodes.W,
|
|
a: Phaser.Input.Keyboard.KeyCodes.A,
|
|
s: Phaser.Input.Keyboard.KeyCodes.S,
|
|
d: Phaser.Input.Keyboard.KeyCodes.D,
|
|
// Arrow Keys
|
|
up: Phaser.Input.Keyboard.KeyCodes.UP,
|
|
down: Phaser.Input.Keyboard.KeyCodes.DOWN,
|
|
left: Phaser.Input.Keyboard.KeyCodes.LEFT,
|
|
right: Phaser.Input.Keyboard.KeyCodes.RIGHT,
|
|
// Actions
|
|
space: Phaser.Input.Keyboard.KeyCodes.SPACE,
|
|
shift: Phaser.Input.Keyboard.KeyCodes.SHIFT,
|
|
r: Phaser.Input.Keyboard.KeyCodes.R,
|
|
x: Phaser.Input.Keyboard.KeyCodes.X, // 🪓 Chopping
|
|
c: Phaser.Input.Keyboard.KeyCodes.C // ⛏️ Mining
|
|
});
|
|
|
|
// Gamepad Events
|
|
this.scene.input.gamepad.on('connected', (pad) => {
|
|
this.setupGamepadEvents(pad);
|
|
});
|
|
|
|
if (this.scene.input.gamepad.total > 0) {
|
|
this.setupGamepadEvents(this.scene.input.gamepad.getPad(0));
|
|
}
|
|
}
|
|
|
|
setupGamepadEvents(pad) {
|
|
if (!pad) return;
|
|
console.log('🎮 Gamepad Connected:', pad.id);
|
|
|
|
pad.on('down', (index) => {
|
|
if (this.isDead) return;
|
|
|
|
// A Button (0) - Attack
|
|
if (index === 0) {
|
|
this.attack();
|
|
}
|
|
|
|
// X Button (2) - Interact (Pick/Tame)
|
|
if (index === 2) {
|
|
const targetX = this.gridX + this.lastDir.x;
|
|
const targetY = this.gridY + this.lastDir.y;
|
|
if (this.scene.interactionSystem) {
|
|
this.scene.interactionSystem.handleInteraction(targetX, targetY);
|
|
}
|
|
}
|
|
|
|
// Y Button (3) - Crafting
|
|
if (index === 3) {
|
|
// Zahteva, da UIScene posluša ta event, ali pa direktni klic
|
|
const ui = this.scene.scene.get('UIScene');
|
|
if (ui) ui.toggleCrafting();
|
|
}
|
|
|
|
// B Button (1) - Inventory
|
|
if (index === 1) {
|
|
const ui = this.scene.scene.get('UIScene');
|
|
if (ui) ui.toggleInventory();
|
|
}
|
|
});
|
|
}
|
|
|
|
attack() {
|
|
console.log('⚔️ Player Attack!');
|
|
|
|
// Attack Sound
|
|
if (this.scene.soundManager) {
|
|
this.scene.soundManager.playAttack();
|
|
}
|
|
|
|
if (this.scene.interactionSystem) {
|
|
const targetX = this.gridX + this.lastDir.x;
|
|
const targetY = this.gridY + this.lastDir.y;
|
|
this.scene.interactionSystem.handleInteraction(targetX, targetY, true); // true = attackMode
|
|
}
|
|
|
|
// Animation
|
|
this.scene.tweens.add({
|
|
targets: this.handSprite,
|
|
angle: 45, // Swing
|
|
yoyo: true,
|
|
duration: 100
|
|
});
|
|
|
|
// Player lunge
|
|
const lungeX = this.sprite.x + (this.lastDir.x * 10);
|
|
const lungeY = this.sprite.y + (this.lastDir.y * 5);
|
|
this.scene.tweens.add({
|
|
targets: this.sprite,
|
|
x: lungeX,
|
|
y: lungeY,
|
|
yoyo: true,
|
|
duration: 50
|
|
});
|
|
}
|
|
|
|
update(delta) {
|
|
// Convert delta to seconds
|
|
const dt = delta / 1000;
|
|
|
|
// 🎮 HANDLE INPUT (always check for input)
|
|
this.handleInput(dt);
|
|
|
|
// 🏃 APPLY SMOOTH MOVEMENT
|
|
// Smoothly interpolate current velocity toward target velocity
|
|
this.velocity.x = Phaser.Math.Linear(
|
|
this.velocity.x,
|
|
this.targetVelocity.x,
|
|
this.acceleration
|
|
);
|
|
this.velocity.y = Phaser.Math.Linear(
|
|
this.velocity.y,
|
|
this.targetVelocity.y,
|
|
this.acceleration
|
|
);
|
|
|
|
// Apply velocity to sprite position
|
|
this.sprite.x += this.velocity.x * dt;
|
|
this.sprite.y += this.velocity.y * dt;
|
|
|
|
// Update grid position from pixel position
|
|
const screenPos = { x: this.sprite.x - this.offsetX, y: this.sprite.y - this.offsetY };
|
|
|
|
// 🎨 FLAT 2D (NEW!) - Direct grid conversion
|
|
this.gridX = Math.floor(screenPos.x / this.tileSize);
|
|
this.gridY = Math.floor(screenPos.y / this.tileSize);
|
|
|
|
// Check if moving
|
|
const speed = Math.sqrt(this.velocity.x ** 2 + this.velocity.y ** 2);
|
|
this.isMoving = speed > 5;
|
|
|
|
// 🎨 UPDATE ANIMATION
|
|
this.updateAnimation();
|
|
|
|
// 💧 ENERGY REGENERATION
|
|
if (!this.sprinting && this.energy < this.maxEnergy) {
|
|
this.energy = Math.min(this.maxEnergy, this.energy + 20 * dt);
|
|
}
|
|
|
|
// ⚡ ENERGY DRAIN WHILE SPRINTING
|
|
if (this.sprinting && this.isMoving && this.energy > 0) {
|
|
this.energy -= this.energyDrain * dt;
|
|
if (this.energy <= 0) {
|
|
this.energy = 0;
|
|
this.sprinting = false; // Can't sprint without energy
|
|
}
|
|
}
|
|
|
|
// 🔧 UPDATE HELD ITEM
|
|
this.updateHeldItem();
|
|
|
|
// 🌱 SPACE KEY - Farming Action
|
|
if (this.keys.space && Phaser.Input.Keyboard.JustDown(this.keys.space)) {
|
|
this.handleFarmingAction();
|
|
}
|
|
|
|
// 🚬 R KEY - Use Item
|
|
if (this.keys.r && Phaser.Input.Keyboard.JustDown(this.keys.r)) {
|
|
this.useSelectedItem();
|
|
}
|
|
|
|
// 🪓 X KEY - Chopping Action
|
|
if (this.keys.x && Phaser.Input.Keyboard.JustDown(this.keys.x)) {
|
|
this.handleChoppingAction();
|
|
}
|
|
|
|
// ⛏️ C KEY - Mining Action
|
|
if (this.keys.c && Phaser.Input.Keyboard.JustDown(this.keys.c)) {
|
|
this.handleMiningAction();
|
|
}
|
|
}
|
|
|
|
updateHeldItem() {
|
|
const uiScene = this.scene.scene.get('UIScene');
|
|
const invSys = this.scene.inventorySystem;
|
|
|
|
if (uiScene && invSys) {
|
|
const selectedIdx = uiScene.selectedSlot;
|
|
const slot = invSys.slots[selectedIdx];
|
|
|
|
if (slot && (slot.type === 'axe' || slot.type === 'pickaxe' || slot.type === 'hoe' || slot.type === 'sword')) {
|
|
const texKey = `item_${slot.type}`;
|
|
if (this.scene.textures.exists(texKey)) {
|
|
this.handSprite.setTexture(texKey);
|
|
this.handSprite.setVisible(true);
|
|
} else {
|
|
this.handSprite.setVisible(false);
|
|
}
|
|
} else {
|
|
this.handSprite.setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
handleInput(dt) {
|
|
// 🛑 STARDEW LOGIC: Block movement if acting
|
|
if (this.isActing) {
|
|
this.velocity.x = 0;
|
|
this.velocity.y = 0;
|
|
return;
|
|
}
|
|
|
|
|
|
// 🎮 COLLECT INPUT FROM ALL SOURCES
|
|
let inputX = 0;
|
|
let inputY = 0;
|
|
|
|
// Keyboard (WASD + Arrows)
|
|
if (this.keys.up.isDown || this.keys.w.isDown) inputY -= 1;
|
|
if (this.keys.down.isDown || this.keys.s.isDown) inputY += 1;
|
|
if (this.keys.left.isDown || this.keys.a.isDown) inputX -= 1;
|
|
if (this.keys.right.isDown || this.keys.d.isDown) inputX += 1;
|
|
|
|
// Virtual Joystick (Mobile)
|
|
const ui = this.scene.scene.get('UIScene');
|
|
if (ui && ui.virtualJoystick) {
|
|
if (ui.virtualJoystick.up) inputY -= 1;
|
|
if (ui.virtualJoystick.down) inputY += 1;
|
|
if (ui.virtualJoystick.left) inputX -= 1;
|
|
if (ui.virtualJoystick.right) inputX += 1;
|
|
}
|
|
|
|
// Gamepad (Xbox Controller)
|
|
if (this.scene.input.gamepad && this.scene.input.gamepad.total > 0) {
|
|
const pad = this.scene.input.gamepad.getPad(0);
|
|
if (pad) {
|
|
const threshold = 0.2;
|
|
const stickY = pad.leftStick.y;
|
|
const stickX = pad.leftStick.x;
|
|
|
|
// Analog stick (smooth values)
|
|
if (Math.abs(stickY) > threshold) inputY += stickY;
|
|
if (Math.abs(stickX) > threshold) inputX += stickX;
|
|
|
|
// D-Pad (digital)
|
|
if (pad.up) inputY -= 1;
|
|
if (pad.down) inputY += 1;
|
|
if (pad.left) inputX -= 1;
|
|
if (pad.right) inputX += 1;
|
|
|
|
// Sprint button (B on Xbox, Circle on PS)
|
|
if (pad.B || pad.buttons[1]?.pressed) {
|
|
this.sprinting = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 🏃 SPRINT DETECTION (Shift key)
|
|
this.sprinting = this.keys.shift?.isDown || this.sprinting;
|
|
|
|
// Normalize diagonal movement (so diagonal isn't faster)
|
|
const inputLength = Math.sqrt(inputX ** 2 + inputY ** 2);
|
|
if (inputLength > 0) {
|
|
inputX /= inputLength;
|
|
inputY /= inputLength;
|
|
}
|
|
|
|
// 🎯 DETERMINE MOVEMENT SPEED
|
|
let maxSpeed = this.moveSpeed;
|
|
if (this.sprinting && this.energy > 0) {
|
|
maxSpeed = this.sprintSpeed;
|
|
}
|
|
|
|
// 🚀 SET TARGET VELOCITY
|
|
this.targetVelocity.x = inputX * maxSpeed;
|
|
this.targetVelocity.y = inputY * maxSpeed;
|
|
|
|
// 🧭 UPDATE DIRECTION & FACING
|
|
if (inputLength > 0.1) {
|
|
// Update last direction for attacks/interactions
|
|
this.lastDir = { x: inputX, y: inputY };
|
|
|
|
// Determine animation direction (4-way)
|
|
// Isometric mapping: up/down = X axis, left/right = Y axis
|
|
let animDir = 'down';
|
|
|
|
// Prioritize primary direction (stronger input)
|
|
if (Math.abs(inputX) > Math.abs(inputY)) {
|
|
// Vertical movement (up/down in isometric)
|
|
animDir = inputX < 0 ? 'up' : 'down';
|
|
} else {
|
|
// Horizontal movement (left/right in isometric)
|
|
animDir = inputY < 0 ? 'right' : 'left';
|
|
}
|
|
|
|
this.direction = animDir;
|
|
}
|
|
}
|
|
|
|
// 🎨 UPDATE ANIMATION (called from update loop)
|
|
updateAnimation() {
|
|
if (!this.sprite.anims) return;
|
|
|
|
const speed = Math.sqrt(this.velocity.x ** 2 + this.velocity.y ** 2);
|
|
|
|
try {
|
|
if (speed < 5) {
|
|
// 😴 IDLE
|
|
const idleAnim = `protagonist_idle_${this.direction}`;
|
|
if (this.scene.anims.exists(idleAnim) && !this.sprite.anims.isPlaying) {
|
|
this.sprite.play(idleAnim);
|
|
}
|
|
} else {
|
|
// 🚶 WALKING / 🏃 SPRINTING
|
|
const walkAnim = `protagonist_walk_${this.direction}`;
|
|
|
|
if (this.scene.anims.exists(walkAnim)) {
|
|
if (this.sprite.anims.currentAnim?.key !== walkAnim) {
|
|
this.sprite.play(walkAnim, true);
|
|
}
|
|
|
|
// Faster animation when sprinting
|
|
const frameRate = this.sprinting ? 12 : 8;
|
|
if (this.sprite.anims.currentAnim) {
|
|
this.sprite.anims.currentAnim.frameRate = frameRate;
|
|
}
|
|
|
|
// 🔉 PHASE 8: Footstep triggers (frames 1 and 3 are usually down-steps)
|
|
const currentFrame = this.sprite.anims.currentFrame ? this.sprite.anims.currentFrame.index : 0;
|
|
if ((currentFrame === 1 || currentFrame === 3) && this.lastFootstepFrame !== currentFrame) {
|
|
if (this.scene.soundManager) {
|
|
// Determine surface
|
|
let surface = 'grass';
|
|
if (this.scene.terrainSystem && this.scene.terrainSystem.getTile) {
|
|
const tile = this.scene.terrainSystem.getTile(this.gridX, this.gridY);
|
|
if (tile && tile.type === 'dirt') surface = 'dirt';
|
|
}
|
|
this.scene.soundManager.playFootstep(surface);
|
|
}
|
|
this.lastFootstepFrame = currentFrame;
|
|
} else if (currentFrame !== 1 && currentFrame !== 3) {
|
|
this.lastFootstepFrame = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hand sprite position update
|
|
const handOffsets = {
|
|
'left': -10,
|
|
'right': 10,
|
|
'up': 0,
|
|
'down': 0
|
|
};
|
|
|
|
if (this.handSprite) {
|
|
this.handSprite.setX(this.sprite.x + (handOffsets[this.direction] || 0));
|
|
}
|
|
|
|
} catch (e) {
|
|
// Ignore animation errors
|
|
}
|
|
}
|
|
|
|
moveToGrid(targetX, targetY) {
|
|
this.isMoving = true;
|
|
this.gridX = targetX;
|
|
this.gridY = targetY;
|
|
|
|
// Footstep Sound
|
|
if (this.scene.soundManager) {
|
|
this.scene.soundManager.playFootstep();
|
|
}
|
|
|
|
const targetScreen = this.iso.toScreen(targetX, targetY);
|
|
|
|
// Play walk animation - SAFE CHECK (updated for protagonist)
|
|
const texKey = this.sprite.texture.key;
|
|
|
|
if (texKey === 'player_protagonist') {
|
|
// KRVAVA ŽETEV: Use directional animations
|
|
const walkAnim = `protagonist_walk_${this.direction}`;
|
|
if (this.scene.anims.exists(walkAnim)) {
|
|
this.sprite.play(walkAnim, true);
|
|
}
|
|
} else if (texKey === 'player_dreadlocks') {
|
|
if (this.scene.anims.exists('player_dreadlocks_walk')) {
|
|
this.sprite.play('player_dreadlocks_walk', true);
|
|
}
|
|
} else if (texKey === 'player_walk') {
|
|
if (this.scene.anims.exists('player_walk_anim')) {
|
|
this.sprite.play('player_walk_anim', true);
|
|
}
|
|
}
|
|
|
|
this.scene.tweens.add({
|
|
targets: [this.sprite, this.handSprite],
|
|
x: '+=' + (targetScreen.x + this.offsetX - this.sprite.x),
|
|
y: '+=' + (targetScreen.y + this.offsetY - this.sprite.y),
|
|
duration: this.gridMoveTime,
|
|
ease: 'Linear',
|
|
onComplete: () => {
|
|
this.isMoving = false;
|
|
this.updatePosition();
|
|
|
|
// Stop animation (updated for protagonist)
|
|
if (texKey === 'player_protagonist') {
|
|
// Play idle animation
|
|
const idleAnim = `protagonist_idle_${this.direction}`;
|
|
if (this.scene.anims.exists(idleAnim)) {
|
|
this.sprite.play(idleAnim);
|
|
} else {
|
|
this.sprite.stop();
|
|
}
|
|
} else if (texKey === 'player_dreadlocks') {
|
|
this.sprite.stop();
|
|
this.sprite.setFrame(0);
|
|
} else if (texKey === 'player_walk') {
|
|
this.sprite.stop();
|
|
this.sprite.setFrame(0);
|
|
}
|
|
}
|
|
});
|
|
|
|
// NOTE: updateDepth() disabled - using sortableObjects Z-sorting in GameScene
|
|
// this.updateDepth();
|
|
}
|
|
|
|
updatePosition() {
|
|
// 🎨 FLAT 2D POSITIONING (NEW!)
|
|
const tileSize = 48;
|
|
|
|
// Direct grid to pixel conversion (NO isometric!)
|
|
const x = Math.round((this.gridX * tileSize) + (tileSize / 2));
|
|
const y = Math.round((this.gridY * tileSize) + (tileSize / 2));
|
|
|
|
this.sprite.setPosition(x, y);
|
|
|
|
const facingRight = !this.sprite.flipX;
|
|
const handOffset = facingRight ? 10 : -10;
|
|
this.handSprite.setPosition(
|
|
Math.round(x + handOffset),
|
|
Math.round(y - 15)
|
|
);
|
|
|
|
this.updateDepth();
|
|
}
|
|
|
|
updateDepth() {
|
|
if (!this.sprite) return;
|
|
|
|
// Optimization: Create dirty check
|
|
if (this.lastDepthY === undefined || Math.abs(this.sprite.y - this.lastDepthY) > 0.1) {
|
|
// Uporabi LAYER_OBJECTS da se pravilno sortira z drevesi/kamni
|
|
const layerBase = this.iso.LAYER_OBJECTS || 200000;
|
|
const depth = layerBase + this.sprite.y;
|
|
|
|
this.sprite.setDepth(depth);
|
|
if (this.handSprite) this.handSprite.setDepth(depth + 1);
|
|
this.lastDepthY = this.sprite.y;
|
|
}
|
|
}
|
|
|
|
getPosition() {
|
|
return { x: this.gridX, y: this.gridY };
|
|
}
|
|
|
|
getScreenPosition() {
|
|
return { x: this.sprite.x, y: this.sprite.y };
|
|
}
|
|
|
|
destroy() {
|
|
if (this.sprite) this.sprite.destroy();
|
|
}
|
|
|
|
dieAnimation() {
|
|
this.sprite.setTint(0xff0000);
|
|
this.scene.tweens.add({
|
|
targets: this.sprite,
|
|
angle: 90,
|
|
duration: 500,
|
|
ease: 'Bounce.easeOut'
|
|
});
|
|
}
|
|
|
|
respawn() {
|
|
this.sprite.clearTint();
|
|
this.sprite.angle = 0;
|
|
}
|
|
|
|
handleFarmingAction() {
|
|
if (!this.scene.farmingSystem) return;
|
|
|
|
const uiScene = this.scene.scene.get('UIScene');
|
|
const invSys = this.scene.inventorySystem;
|
|
|
|
if (!uiScene || !invSys) return;
|
|
|
|
const selectedIdx = uiScene.selectedSlot;
|
|
const slot = invSys.slots[selectedIdx];
|
|
const itemType = slot ? slot.type : null;
|
|
|
|
// Get tile player is standing on
|
|
const gridX = this.gridX;
|
|
const gridY = this.gridY;
|
|
|
|
// HOE - Till soil
|
|
if (itemType === 'hoe') {
|
|
const success = this.scene.farmingSystem.tillSoil(gridX, gridY);
|
|
if (success) {
|
|
console.log('✅ Tilled soil!');
|
|
// Particle effect - soil spray
|
|
this.createSoilParticles(gridX, gridY);
|
|
// Tool swing animation
|
|
this.swingTool();
|
|
// TODO: Play dig sound
|
|
}
|
|
return;
|
|
}
|
|
|
|
// SEEDS - Plant
|
|
if (itemType === 'carrot' || itemType === 'wheat' || itemType === 'item_seeds') {
|
|
const cropType = itemType === 'item_seeds' ? 'carrot' : itemType;
|
|
const success = this.scene.farmingSystem.plantSeed(gridX, gridY, cropType);
|
|
if (success) {
|
|
invSys.removeItem(itemType, 1);
|
|
console.log('🌱 Planted seed!');
|
|
// Particle effect - seed drop
|
|
this.createSeedParticles(gridX, gridY);
|
|
// TODO: Play plant sound
|
|
}
|
|
return;
|
|
}
|
|
|
|
// EMPTY HAND - Harvest
|
|
if (!itemType) {
|
|
const success = this.scene.farmingSystem.harvestCrop(gridX, gridY);
|
|
if (success) {
|
|
console.log('🌾 Harvested crop!');
|
|
// Particle effect - harvest sparkle
|
|
this.createHarvestParticles(gridX, gridY);
|
|
// Camera shake
|
|
this.scene.cameras.main.shake(200, 0.003);
|
|
// TODO: Play harvest sound
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
swingTool() {
|
|
if (!this.handSprite || !this.handSprite.visible) return;
|
|
|
|
// Save original position
|
|
const originalAngle = this.handSprite.angle;
|
|
const originalScale = this.handSprite.scaleX;
|
|
|
|
// Swing animation
|
|
this.scene.tweens.add({
|
|
targets: this.handSprite,
|
|
angle: originalAngle - 45,
|
|
scaleX: originalScale * 1.3,
|
|
scaleY: originalScale * 1.3,
|
|
duration: 100,
|
|
yoyo: true,
|
|
ease: 'Cubic.easeOut',
|
|
onComplete: () => {
|
|
this.handSprite.angle = originalAngle;
|
|
this.handSprite.scaleX = originalScale;
|
|
this.handSprite.scaleY = originalScale;
|
|
}
|
|
});
|
|
}
|
|
|
|
createSoilParticles(gridX, gridY) {
|
|
const screenPos = this.scene.iso.gridToScreen(gridX, gridY);
|
|
const x = screenPos.x + this.offsetX;
|
|
const y = screenPos.y + this.offsetY;
|
|
|
|
// Brown soil particles
|
|
for (let i = 0; i < 10; i++) {
|
|
const particle = this.scene.add.circle(x, y, 3, 0x8B4513);
|
|
this.scene.tweens.add({
|
|
targets: particle,
|
|
x: x + (Math.random() - 0.5) * 30,
|
|
y: y - Math.random() * 20,
|
|
alpha: 0,
|
|
duration: 400,
|
|
onComplete: () => particle.destroy()
|
|
});
|
|
}
|
|
}
|
|
|
|
createSeedParticles(gridX, gridY) {
|
|
const screenPos = this.scene.iso.gridToScreen(gridX, gridY);
|
|
const x = screenPos.x + this.offsetX;
|
|
const y = screenPos.y + this.offsetY;
|
|
|
|
// Green seed particles
|
|
for (let i = 0; i < 5; i++) {
|
|
const particle = this.scene.add.circle(x, y - 20, 2, 0x00ff00);
|
|
this.scene.tweens.add({
|
|
targets: particle,
|
|
y: y,
|
|
alpha: 0,
|
|
duration: 500,
|
|
ease: 'Cubic.easeIn',
|
|
onComplete: () => particle.destroy()
|
|
});
|
|
}
|
|
}
|
|
|
|
createHarvestParticles(gridX, gridY) {
|
|
const screenPos = this.scene.iso.gridToScreen(gridX, gridY);
|
|
const x = screenPos.x + this.offsetX;
|
|
const y = screenPos.y + this.offsetY;
|
|
|
|
// Golden sparkle particles
|
|
for (let i = 0; i < 15; i++) {
|
|
const particle = this.scene.add.circle(x, y, 4, 0xFFD700);
|
|
this.scene.tweens.add({
|
|
targets: particle,
|
|
x: x + (Math.random() - 0.5) * 40,
|
|
y: y - Math.random() * 40,
|
|
scaleX: 0,
|
|
scaleY: 0,
|
|
alpha: 0,
|
|
duration: 600,
|
|
ease: 'Cubic.easeOut',
|
|
onComplete: () => particle.destroy()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if player has required tool equipped/in inventory
|
|
* @param {string} toolType - Tool type (axe, pickaxe, hoe, etc.)
|
|
* @returns {boolean} - True if player has the tool
|
|
*/
|
|
hasToolEquipped(toolType) {
|
|
if (!toolType) return true; // No tool required
|
|
|
|
// Check inventory for tool
|
|
if (this.scene.inventorySystem) {
|
|
return this.scene.inventorySystem.hasItem(toolType, 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
useSelectedItem() {
|
|
const uiScene = this.scene.scene.get('UIScene');
|
|
const invSys = this.scene.inventorySystem;
|
|
|
|
if (!uiScene || !invSys) return;
|
|
|
|
const selectedIdx = uiScene.selectedSlot;
|
|
const slot = invSys.slots[selectedIdx];
|
|
if (!slot || slot.count <= 0) return;
|
|
|
|
console.log(`🌀 Attempting to use item: ${slot.type}`);
|
|
|
|
if (slot.type === 'cannabis_buds') {
|
|
// SMOKE IT!
|
|
if (this.scene.statusEffectSystem) {
|
|
const used = this.scene.statusEffectSystem.applyEffect('high');
|
|
if (used) {
|
|
invSys.removeItem(slot.type, 1);
|
|
// Audio feedback
|
|
if (this.scene.soundManager) this.scene.soundManager.playSmokeSound();
|
|
// Visual feedback
|
|
this.createSmokeParticles(this.gridX, this.gridY);
|
|
this.scene.events.emit('show-floating-text', {
|
|
x: this.sprite.x, y: this.sprite.y - 60, text: '🚬 *puff puff*', color: '#55ff55'
|
|
});
|
|
}
|
|
}
|
|
} else if (slot.type === 'health_potion' || slot.type === 'medicine') {
|
|
// Generic healing
|
|
if (this.hp < this.maxHp) {
|
|
this.hp = Math.min(this.maxHp, this.hp + 20);
|
|
invSys.removeItem(slot.type, 1);
|
|
this.scene.events.emit('show-floating-text', {
|
|
x: this.sprite.x, y: this.sprite.y - 60, text: '💖 Recovered!', color: '#ff5555'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
createSmokeParticles(gridX, gridY) {
|
|
// Reuse particle logic, or create locally
|
|
for (let i = 0; i < 10; i++) {
|
|
const smoke = this.scene.add.circle(this.sprite.x, this.sprite.y - 40, 4 + Math.random() * 4, 0xcccccc, 0.6);
|
|
this.scene.tweens.add({
|
|
targets: smoke,
|
|
x: smoke.x + (Math.random() - 0.5) * 50,
|
|
y: smoke.y - 100 - Math.random() * 50,
|
|
alpha: 0,
|
|
scale: 3,
|
|
duration: 2000 + Math.random() * 1000,
|
|
onComplete: () => smoke.destroy()
|
|
});
|
|
}
|
|
}
|
|
|
|
handleChoppingAction() {
|
|
console.log('🪓 Chopping action!');
|
|
|
|
// Check if player has axe equipped
|
|
const uiScene = this.scene.scene.get('UIScene');
|
|
const invSys = this.scene.inventorySystem;
|
|
|
|
if (!uiScene || !invSys) return;
|
|
|
|
const selectedIdx = uiScene.selectedSlot;
|
|
const slot = invSys.slots[selectedIdx];
|
|
|
|
if (!slot || slot.type !== 'axe') {
|
|
console.log('⚠️ No axe equipped!');
|
|
return;
|
|
}
|
|
|
|
// Calculate target tile in front of player
|
|
const targetX = this.gridX + Math.round(this.lastDir.x);
|
|
const targetY = this.gridY + Math.round(this.lastDir.y);
|
|
|
|
// Trigger interaction system with chopping mode
|
|
if (this.scene.interactionSystem) {
|
|
this.scene.interactionSystem.handleInteraction(targetX, targetY, true);
|
|
}
|
|
|
|
// Play chopping animation (swing tool)
|
|
this.swingTool();
|
|
|
|
// Play sound
|
|
if (this.scene.soundManager && this.scene.soundManager.playChop) {
|
|
this.scene.soundManager.playChop();
|
|
}
|
|
}
|
|
|
|
handleMiningAction() {
|
|
console.log('⛏️ Mining action!');
|
|
|
|
// Check if player has pickaxe equipped
|
|
const uiScene = this.scene.scene.get('UIScene');
|
|
const invSys = this.scene.inventorySystem;
|
|
|
|
if (!uiScene || !invSys) return;
|
|
|
|
const selectedIdx = uiScene.selectedSlot;
|
|
const slot = invSys.slots[selectedIdx];
|
|
|
|
if (!slot || slot.type !== 'pickaxe') {
|
|
console.log('⚠️ No pickaxe equipped!');
|
|
return;
|
|
}
|
|
|
|
// Calculate target tile in front of player
|
|
const targetX = this.gridX + Math.round(this.lastDir.x);
|
|
const targetY = this.gridY + Math.round(this.lastDir.y);
|
|
|
|
// Trigger interaction system with mining mode
|
|
if (this.scene.interactionSystem) {
|
|
this.scene.interactionSystem.handleInteraction(targetX, targetY, true);
|
|
}
|
|
|
|
// Play mining animation (swing tool)
|
|
this.swingTool();
|
|
|
|
// Play sound
|
|
if (this.scene.soundManager && this.scene.soundManager.playMine) {
|
|
this.scene.soundManager.playMine();
|
|
}
|
|
}
|
|
}
|