import * as THREE from 'three'; export class Dog { constructor(game) { this.game = game; this.mesh = null; this.position = new THREE.Vector3(-25, 0, 30); // default, обновляется из конфига в spawn() this.adopted = false; this.followDistance = 3; this.speed = 4; this.moodTimer = 0; this.tailWag = 0; this.tail = null; } spawn() { // Позиция из конфига (рядом с парком) const parkCfg = this.game.world.mapConfig?.structures?.park || {}; this.position.set( (parkCfg.x ?? -30) + 5, 0, (parkCfg.z ?? 25) + 5 ); const group = new THREE.Group(); // Тело const bodyGeo = new THREE.BoxGeometry(0.8, 0.45, 0.4); const bodyMat = new THREE.MeshStandardMaterial({ color: 0x8B6914 }); const body = new THREE.Mesh(bodyGeo, bodyMat); body.position.y = 0.45; body.castShadow = true; group.add(body); // Голова const headGeo = new THREE.BoxGeometry(0.3, 0.3, 0.35); const head = new THREE.Mesh(headGeo, bodyMat); head.position.set(0.45, 0.6, 0); head.castShadow = true; group.add(head); // Морда const noseGeo = new THREE.BoxGeometry(0.12, 0.12, 0.2); const noseMat = new THREE.MeshStandardMaterial({ color: 0x4a3010 }); const nose = new THREE.Mesh(noseGeo, noseMat); nose.position.set(0.6, 0.55, 0); group.add(nose); // Нос const noseTip = new THREE.Mesh( new THREE.SphereGeometry(0.04, 6, 4), new THREE.MeshStandardMaterial({ color: 0x222222 }) ); noseTip.position.set(0.67, 0.56, 0); group.add(noseTip); // Уши [-0.14, 0.14].forEach(side => { const earGeo = new THREE.BoxGeometry(0.08, 0.15, 0.06); const ear = new THREE.Mesh(earGeo, bodyMat); ear.position.set(0.4, 0.8, side); ear.rotation.z = side > 0 ? 0.3 : -0.3; group.add(ear); }); // Глаза const eyeMat = new THREE.MeshStandardMaterial({ color: 0x222222 }); [-0.1, 0.1].forEach(side => { const eye = new THREE.Mesh(new THREE.SphereGeometry(0.03, 6, 4), eyeMat); eye.position.set(0.55, 0.65, side); group.add(eye); }); // Ноги const legGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.3, 6); const legMat = new THREE.MeshStandardMaterial({ color: 0x7a5a10 }); [[-0.25, -0.14], [-0.25, 0.14], [0.25, -0.14], [0.25, 0.14]].forEach(([lx, lz]) => { const leg = new THREE.Mesh(legGeo, legMat); leg.position.set(lx, 0.15, lz); group.add(leg); }); // Хвост const tailGeo = new THREE.CylinderGeometry(0.03, 0.02, 0.3, 6); const tail = new THREE.Mesh(tailGeo, bodyMat); tail.position.set(-0.5, 0.6, 0); tail.rotation.z = Math.PI / 4; group.add(tail); this.tail = tail; group.position.copy(this.position); this.mesh = group; this.game.scene.add(group); } adopt() { this.adopted = true; this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 20); } update(dt) { if (!this.mesh) return; // Виляние хвостом this.tailWag += dt * 8; if (this.tail) { this.tail.rotation.x = Math.sin(this.tailWag) * 0.4; } if (this.adopted) { this.followPlayer(dt); this.applyMoodBoost(dt); } else { this.wander(dt); } this.mesh.position.copy(this.position); } followPlayer(dt) { const playerPos = this.game.player.position; const dir = new THREE.Vector3().subVectors(playerPos, this.position); dir.y = 0; const dist = dir.length(); if (dist > this.followDistance) { dir.normalize(); const speed = dist > 8 ? this.speed * 2 : this.speed; this.position.add(dir.multiplyScalar(speed * dt)); // Поворот к игроку this.mesh.rotation.y = Math.atan2(dir.x, dir.z); } // Телепортация если слишком далеко if (dist > 25) { const behind = new THREE.Vector3(); this.game.camera.getWorldDirection(behind); behind.y = 0; behind.normalize().multiplyScalar(-3); this.position.copy(playerPos).add(behind); } } wander(dt) { // Бродит рядом с парком if (!this._wanderTarget || this.position.distanceTo(this._wanderTarget) < 1) { const parkCfg = this.game.world.mapConfig?.structures?.park || {}; const cx = parkCfg.x ?? -30; const cz = parkCfg.z ?? 25; this._wanderTarget = new THREE.Vector3( cx + (Math.random() - 0.5) * 20, 0, cz + (Math.random() - 0.5) * 16 ); this._wanderWait = 2 + Math.random() * 3; } if (this._wanderWait > 0) { this._wanderWait -= dt; return; } const dir = new THREE.Vector3().subVectors(this._wanderTarget, this.position); dir.y = 0; if (dir.length() > 0.5) { dir.normalize(); this.position.add(dir.multiplyScalar(1.5 * dt)); this.mesh.rotation.y = Math.atan2(dir.x, dir.z); } } applyMoodBoost(dt) { this.moodTimer += dt; if (this.moodTimer >= 10) { this.moodTimer = 0; this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 1); } } reset() { if (this.mesh) { this.game.scene.remove(this.mesh); this.mesh = null; } this.adopted = false; const parkCfg = this.game.world.mapConfig?.structures?.park || {}; this.position.set( (parkCfg.x ?? -30) + 5, 0, (parkCfg.z ?? 25) + 5 ); } }