udomacenje zombija in uboj\
This commit is contained in:
@@ -14,14 +14,17 @@ class NPC {
|
||||
this.iso = new IsometricUtils(48, 24);
|
||||
|
||||
// Random walk paramters
|
||||
this.moveSpeed = 100; // px/s (počasnejše od igralca)
|
||||
this.gridMoveTime = 300; // ms za premik (počasneje)
|
||||
this.moveSpeed = 100; // px/s
|
||||
this.gridMoveTime = 300; // ms
|
||||
|
||||
// Stanje
|
||||
this.state = 'WANDER'; // WANDER, TAMED, FOLLOW
|
||||
this.isMoving = false;
|
||||
this.pauseTime = 0;
|
||||
this.maxPauseTime = 2000; // Pavza med premiki (2s)
|
||||
this.maxPauseTime = 2000;
|
||||
this.attackCooldownTimer = 0;
|
||||
this.hp = 20;
|
||||
this.maxHp = 20;
|
||||
|
||||
// Kreira sprite
|
||||
this.createSprite();
|
||||
@@ -57,15 +60,47 @@ class NPC {
|
||||
|
||||
// Change color slightly to indicate friend
|
||||
this.sprite.setTint(0xAAFFAA);
|
||||
|
||||
// Hide Health Bar if tamed
|
||||
if (this.healthBarBg) {
|
||||
this.healthBarBg.setVisible(false);
|
||||
this.healthBar.setVisible(false);
|
||||
}
|
||||
|
||||
// Change Eyes to Friendly (Cyan)
|
||||
this.addTamedEyes();
|
||||
}
|
||||
|
||||
addTamedEyes() {
|
||||
if (this.eyesGroup) return;
|
||||
|
||||
// Container for eyes
|
||||
// Coordinates relative to sprite center bottom (0.5, 1)
|
||||
// Head is roughly at y - height.
|
||||
// Assuming sprite height ~50-60px visual.
|
||||
|
||||
this.eyesGroup = this.scene.add.container(this.sprite.x, this.sprite.y);
|
||||
|
||||
// Eyes
|
||||
const eyeL = this.scene.add.rectangle(-5, -55, 3, 3, 0x00FFFF);
|
||||
const eyeR = this.scene.add.rectangle(5, -55, 3, 3, 0x00FFFF);
|
||||
|
||||
this.eyesGroup.add([eyeL, eyeR]);
|
||||
this.eyesGroup.setDepth(this.sprite.depth + 2);
|
||||
}
|
||||
|
||||
toggleState() {
|
||||
if (this.state === 'WANDER') {
|
||||
this.tame();
|
||||
} else {
|
||||
// Maybe command to stay/follow?
|
||||
// Command to stay/follow
|
||||
this.state = this.state === 'FOLLOW' ? 'STAY' : 'FOLLOW';
|
||||
console.log(`Command: ${this.state}`);
|
||||
|
||||
// Visual feedback for command
|
||||
const txt = this.scene.add.text(this.sprite.x, this.sprite.y - 60, this.state, { fontSize: '12px', color: '#FFF' });
|
||||
txt.setOrigin(0.5);
|
||||
this.scene.tweens.add({ targets: txt, y: txt.y - 20, alpha: 0, duration: 1000, onComplete: () => txt.destroy() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +135,21 @@ class NPC {
|
||||
this.sprite.setScale(0.3);
|
||||
}
|
||||
|
||||
// HEALTH BAR
|
||||
this.healthBarBg = this.scene.add.graphics();
|
||||
this.healthBarBg.fillStyle(0x000000, 0.5);
|
||||
this.healthBarBg.fillRect(-16, -70, 32, 4);
|
||||
this.healthBarBg.setVisible(false);
|
||||
|
||||
this.healthBar = this.scene.add.graphics();
|
||||
this.healthBar.fillStyle(0x00ff00, 1);
|
||||
this.healthBar.fillRect(-16, -70, 32, 4);
|
||||
this.healthBar.setVisible(false);
|
||||
|
||||
// Depth sorting
|
||||
this.updateDepth();
|
||||
}
|
||||
|
||||
// ... loop update ...
|
||||
|
||||
moveToGrid(targetX, targetY) {
|
||||
// Determine facing direction before moving
|
||||
const dx = targetX - this.gridX;
|
||||
@@ -133,6 +177,7 @@ class NPC {
|
||||
ease: 'Linear',
|
||||
onComplete: () => {
|
||||
this.isMoving = false;
|
||||
this.updatePosition();
|
||||
|
||||
// Stop Animation
|
||||
if (this.sprite.texture.key === 'zombie_walk') {
|
||||
@@ -147,106 +192,259 @@ class NPC {
|
||||
}
|
||||
|
||||
update(delta) {
|
||||
this.updateDepth(); // Continuous depth sorting
|
||||
// 1. Distance Culling
|
||||
if (this.scene.player) {
|
||||
const playerPos = this.scene.player.getPosition();
|
||||
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y);
|
||||
const cullDist = (this.state === 'CHASE' || this.state === 'FOLLOW') ? 50 : 30;
|
||||
|
||||
if (this.isMoving) {
|
||||
return; // Že se premika
|
||||
if (dist > cullDist) {
|
||||
if (this.sprite.visible) {
|
||||
this.sprite.setVisible(false);
|
||||
if (this.healthBar) {
|
||||
this.healthBar.setVisible(false);
|
||||
this.healthBarBg.setVisible(false);
|
||||
}
|
||||
if (this.eyesGroup) this.eyesGroup.setVisible(false);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (!this.sprite.visible) {
|
||||
this.sprite.setVisible(true);
|
||||
if (this.eyesGroup) this.eyesGroup.setVisible(true);
|
||||
// Bars stay hidden until damaged usually
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tamed logic
|
||||
// 2. Optimization: Update depth ONLY if moving
|
||||
if (this.isMoving) {
|
||||
this.updateDepth();
|
||||
this.updatePosition();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. AI Logic
|
||||
if (this.type === 'zombie' && this.state !== 'TAMED' && this.state !== 'FOLLOW') {
|
||||
this.handleAggressiveAI(delta);
|
||||
} else {
|
||||
this.handlePassiveAI(delta);
|
||||
}
|
||||
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
handlePassiveAI(delta) {
|
||||
if (this.state === 'TAMED' || this.state === 'FOLLOW') {
|
||||
this.followPlayer();
|
||||
return;
|
||||
}
|
||||
|
||||
// Random walk - pavza med premiki
|
||||
this.pauseTime += delta;
|
||||
|
||||
if (this.pauseTime >= this.maxPauseTime) {
|
||||
this.performRandomWalk();
|
||||
this.pauseTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
followPlayer() {
|
||||
handleAggressiveAI(delta) {
|
||||
if (!this.scene.player) return;
|
||||
|
||||
const playerPos = this.scene.player.getPosition();
|
||||
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y);
|
||||
|
||||
// Če je precej dlje, se premakni k njemu
|
||||
if (dist > 2) {
|
||||
const dx = Math.sign(playerPos.x - this.gridX);
|
||||
const dy = Math.sign(playerPos.y - this.gridY);
|
||||
if (this.attackCooldownTimer > 0) {
|
||||
this.attackCooldownTimer -= delta;
|
||||
}
|
||||
|
||||
// Move 1 tile towards player
|
||||
let targetX = this.gridX + dx;
|
||||
let targetY = this.gridY + dy;
|
||||
|
||||
// Avoid occupying SAME tile as player
|
||||
if (targetX === playerPos.x && targetY === playerPos.y) {
|
||||
if (Math.random() < 0.5) targetX = this.gridX;
|
||||
else targetY = this.gridY;
|
||||
if (this.state === 'WANDER') {
|
||||
if (dist < 8) {
|
||||
this.state = 'CHASE';
|
||||
this.showEmote('!');
|
||||
} else {
|
||||
this.pauseTime += delta;
|
||||
if (this.pauseTime >= this.maxPauseTime) {
|
||||
this.performRandomWalk();
|
||||
this.pauseTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this.state === 'CHASE') {
|
||||
if (dist > 15) {
|
||||
this.state = 'WANDER';
|
||||
return;
|
||||
}
|
||||
if (dist <= 1.5) {
|
||||
this.tryAttack();
|
||||
} else {
|
||||
this.moveTowards(playerPos.x, playerPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.moveToGrid(targetX, targetY);
|
||||
moveTowards(targetX, targetY) {
|
||||
const dx = Math.sign(targetX - this.gridX);
|
||||
const dy = Math.sign(targetY - this.gridY);
|
||||
let nextX = this.gridX + dx;
|
||||
let nextY = this.gridY + dy;
|
||||
|
||||
if (this.isValidMove(nextX, nextY)) {
|
||||
this.moveToGrid(nextX, nextY);
|
||||
} else if (this.isValidMove(this.gridX + dx, this.gridY)) {
|
||||
this.moveToGrid(this.gridX + dx, this.gridY);
|
||||
} else if (this.isValidMove(this.gridX, this.gridY + dy)) {
|
||||
this.moveToGrid(this.gridX, this.gridY + dy);
|
||||
}
|
||||
}
|
||||
|
||||
isValidMove(x, y) {
|
||||
const terrainSystem = this.scene.terrainSystem;
|
||||
if (!terrainSystem) return true;
|
||||
if (!this.iso.isInBounds(x, y, terrainSystem.width, terrainSystem.height)) return false;
|
||||
if (terrainSystem.tiles[y][x].type.name === 'water') return false;
|
||||
|
||||
const key = `${x},${y}`;
|
||||
if (terrainSystem.decorationsMap.has(key)) {
|
||||
const decor = terrainSystem.decorationsMap.get(key);
|
||||
const solidTypes = ['tree', 'stone', 'bush', 'wall', 'ruin', 'fence', 'house', 'gravestone'];
|
||||
if (solidTypes.includes(decor.type)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
tryAttack() {
|
||||
if (this.attackCooldownTimer > 0) return;
|
||||
this.attackCooldownTimer = 1500;
|
||||
|
||||
if (this.sprite) {
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
y: this.sprite.y - 10,
|
||||
yoyo: true, duration: 100, repeat: 1
|
||||
});
|
||||
}
|
||||
|
||||
if (this.scene.statsSystem) {
|
||||
this.scene.statsSystem.takeDamage(10);
|
||||
this.scene.cameras.main.shake(100, 0.005);
|
||||
}
|
||||
}
|
||||
|
||||
performRandomWalk() {
|
||||
// Naključna smer (NSEW + možnost obstati)
|
||||
const directions = [
|
||||
{ x: -1, y: 0 }, // North-West
|
||||
{ x: 1, y: 0 }, // South-East
|
||||
{ x: 0, y: -1 }, // South-West
|
||||
{ x: 0, y: 1 }, // North-East
|
||||
{ x: 0, y: 0 } // Stay (30% možnost)
|
||||
];
|
||||
|
||||
const dir = Phaser.Math.RND.pick(directions);
|
||||
const targetX = this.gridX + dir.x;
|
||||
const targetY = this.gridY + dir.y;
|
||||
|
||||
// Preveri kolizijo z robovi
|
||||
const terrainSystem = this.scene.terrainSystem;
|
||||
if (terrainSystem && this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) {
|
||||
// Preveri da ni ista pozicija kot igralec
|
||||
if (this.scene.player) {
|
||||
const playerPos = this.scene.player.getPosition();
|
||||
if (targetX === playerPos.x && targetY === playerPos.y) {
|
||||
return; // Ne premakni se na igralca
|
||||
}
|
||||
}
|
||||
|
||||
if (dir.x !== 0 || dir.y !== 0) {
|
||||
this.moveToGrid(targetX, targetY);
|
||||
}
|
||||
const dirs = [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 1, y: 0 }];
|
||||
const dir = dirs[Math.floor(Math.random() * dirs.length)];
|
||||
const nextX = this.gridX + dir.x;
|
||||
const nextY = this.gridY + dir.y;
|
||||
if (this.isValidMove(nextX, nextY)) {
|
||||
this.moveToGrid(nextX, nextY);
|
||||
}
|
||||
}
|
||||
|
||||
followPlayer() {
|
||||
if (!this.scene.player) return;
|
||||
const playerPos = this.scene.player.getPosition();
|
||||
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y);
|
||||
|
||||
if (dist < 2) return;
|
||||
this.moveTowards(playerPos.x, playerPos.y);
|
||||
}
|
||||
|
||||
showEmote(text) {
|
||||
const emote = this.scene.add.text(this.sprite.x, this.sprite.y - 40, text, { fontSize: '20px', fontStyle: 'bold', color: '#FF0000' });
|
||||
emote.setOrigin(0.5);
|
||||
emote.setDepth(this.sprite.depth + 100);
|
||||
this.scene.tweens.add({ targets: emote, y: emote.y - 20, alpha: 0, duration: 1000, onComplete: () => emote.destroy() });
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
if (!this.sprite) return;
|
||||
|
||||
const screenPos = this.iso.toScreen(this.gridX, this.gridY);
|
||||
this.sprite.setPosition(
|
||||
screenPos.x + this.offsetX,
|
||||
screenPos.y + this.offsetY
|
||||
);
|
||||
|
||||
if (!this.isMoving) {
|
||||
this.sprite.setPosition(
|
||||
screenPos.x + this.offsetX,
|
||||
screenPos.y + this.offsetY
|
||||
);
|
||||
}
|
||||
|
||||
// BARS
|
||||
if (this.healthBar && this.healthBarBg) {
|
||||
this.healthBarBg.x = this.sprite.x;
|
||||
this.healthBarBg.y = this.sprite.y;
|
||||
this.healthBar.x = this.sprite.x;
|
||||
this.healthBar.y = this.sprite.y;
|
||||
|
||||
this.healthBarBg.setDepth(this.sprite.depth + 100);
|
||||
this.healthBar.setDepth(this.sprite.depth + 101);
|
||||
}
|
||||
|
||||
// EYES
|
||||
if (this.eyesGroup) {
|
||||
this.eyesGroup.setPosition(this.sprite.x, this.sprite.y);
|
||||
this.eyesGroup.setDepth(this.sprite.depth + 2);
|
||||
}
|
||||
|
||||
this.updateDepth();
|
||||
}
|
||||
|
||||
updateDepth() {
|
||||
// Pixel perfect sorting
|
||||
if (this.sprite) this.sprite.setDepth(this.sprite.y);
|
||||
if (this.sprite) {
|
||||
this.sprite.setDepth(this.sprite.y);
|
||||
}
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
return { x: this.gridX, y: this.gridY };
|
||||
takeDamage(amount) {
|
||||
this.hp -= amount;
|
||||
|
||||
// Show Health Bar
|
||||
if (this.healthBar) {
|
||||
this.healthBar.setVisible(true);
|
||||
this.healthBarBg.setVisible(true);
|
||||
|
||||
// Update width
|
||||
const percent = Math.max(0, this.hp / this.maxHp);
|
||||
this.healthBar.clear();
|
||||
this.healthBar.fillStyle(percent < 0.3 ? 0xff0000 : 0x00ff00, 1);
|
||||
this.healthBar.fillRect(-16, -70, 32 * percent, 4);
|
||||
}
|
||||
|
||||
if (this.sprite) {
|
||||
this.sprite.setTint(0xff0000);
|
||||
this.scene.time.delayedCall(100, () => {
|
||||
if (this.sprite) {
|
||||
this.sprite.clearTint();
|
||||
// Re-apply tamed tint if tamed
|
||||
if (this.state === 'TAMED' || this.state === 'FOLLOW' || this.state === 'STAY') {
|
||||
this.sprite.setTint(0xAAFFAA);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Zombie HP: ${this.hp}`);
|
||||
|
||||
if (this.hp <= 0) {
|
||||
this.die();
|
||||
}
|
||||
}
|
||||
|
||||
die() {
|
||||
console.log('🧟💀 Zombie DEAD');
|
||||
// Spawn loot - BONE
|
||||
if (this.scene.interactionSystem && this.scene.interactionSystem.spawnLoot) {
|
||||
this.scene.interactionSystem.spawnLoot(this.gridX, this.gridY, 'item_bone');
|
||||
}
|
||||
this.destroy();
|
||||
|
||||
const idx = this.scene.npcs.indexOf(this);
|
||||
if (idx > -1) this.scene.npcs.splice(idx, 1);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.sprite) {
|
||||
this.sprite.destroy();
|
||||
}
|
||||
if (this.sprite) this.sprite.destroy();
|
||||
if (this.healthBar) this.healthBar.destroy();
|
||||
if (this.healthBarBg) this.healthBarBg.destroy();
|
||||
if (this.eyesGroup) this.eyesGroup.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user