import * as THREE from 'three'; export class Police { constructor(game) { this.game = game; this.officers = []; this.warningCooldown = 0; } spawnPatrols() { this.officers = []; // Два патруля по дорогам const routes = [ // Патруль 1: вдоль главной E-W дороги (тротуар z=9) [ { x: -50, z: 9 }, { x: -10, z: 9 }, { x: 25, z: 9 }, { x: 50, z: 9 }, { x: 25, z: 9 }, { x: -10, z: 9 }, ], // Патруль 2: вдоль N-S (тротуар x=9) и южной [ { x: 9, z: -35 }, { x: 9, z: -5 }, { x: 9, z: 15 }, { x: 9, z: 40 }, { x: 9, z: 15 }, { x: 9, z: -5 }, ], ]; routes.forEach((waypoints, i) => { const officer = { position: new THREE.Vector3(waypoints[0].x, 0, waypoints[0].z), waypoints, currentWP: 0, state: 'patrol', // patrol, warning, chase, fine speed: 3, chaseSpeed: 5.5, detectRange: 20, warnTimer: 0, mesh: null, warningGiven: false, }; officer.mesh = this.createOfficerMesh(); officer.mesh.position.copy(officer.position); this.game.scene.add(officer.mesh); this.officers.push(officer); }); } createOfficerMesh() { const group = new THREE.Group(); // Тело (синяя форма) const bodyMat = new THREE.MeshStandardMaterial({ color: 0x1a3366 }); const body = new THREE.Mesh(new THREE.CylinderGeometry(0.32, 0.37, 1.15, 8), bodyMat); body.position.y = 0.85; body.castShadow = true; group.add(body); // Голова const head = new THREE.Mesh( new THREE.SphereGeometry(0.23, 8, 6), new THREE.MeshStandardMaterial({ color: 0xc49070 }) ); head.position.y = 1.57; group.add(head); // Фуражка const capBrim = new THREE.Mesh( new THREE.CylinderGeometry(0.28, 0.28, 0.04, 8), new THREE.MeshStandardMaterial({ color: 0x1a2244 }) ); capBrim.position.y = 1.72; group.add(capBrim); const capTop = new THREE.Mesh( new THREE.CylinderGeometry(0.18, 0.22, 0.14, 8), new THREE.MeshStandardMaterial({ color: 0x1a2244 }) ); capTop.position.y = 1.80; group.add(capTop); // Ноги const legMat = new THREE.MeshStandardMaterial({ color: 0x1a2244 }); [-0.12, 0.12].forEach(side => { const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 0.5, 6), legMat); leg.position.set(side, 0.25, 0); leg.castShadow = true; group.add(leg); }); return group; } update(dt) { if (this.warningCooldown > 0) this.warningCooldown -= dt; // Ночью полиция не патрулирует const hour = this.game.gameTime / 60; const isNightTime = hour < 6 || hour > 22; this.officers.forEach(officer => { if (isNightTime) { // Уходят за карту ночью if (officer.mesh) officer.mesh.visible = false; officer.state = 'patrol'; return; } if (officer.mesh) officer.mesh.visible = true; switch (officer.state) { case 'patrol': this.updatePatrol(officer, dt); this.checkViolation(officer); break; case 'warning': this.updateWarning(officer, dt); break; case 'chase': this.updateChase(officer, dt); break; case 'fine': // Dialog in progress, do nothing break; } }); } updatePatrol(officer, dt) { const wp = officer.waypoints[officer.currentWP]; const target = new THREE.Vector3(wp.x, 0, wp.z); const dir = target.clone().sub(officer.position); const dist = dir.length(); if (dist < 1) { officer.currentWP = (officer.currentWP + 1) % officer.waypoints.length; } else { dir.normalize(); officer.position.add(dir.multiplyScalar(officer.speed * dt)); officer.mesh.position.copy(officer.position); officer.mesh.rotation.y = Math.atan2(dir.x, dir.z); } } checkViolation(officer) { const playerPos = this.game.player.position; const dist = officer.position.distanceTo(playerPos); const detectRange = this.getDetectRange(); if (dist > detectRange) return; // Попрошайничество в зоне видимости if (this.game.player.isBegging) { officer.state = 'warning'; officer.warnTimer = 3; officer.warningGiven = true; this.game.notify('Полиция: "Прекратите попрошайничать!"', 'bad'); return; } // Очень низкая репутация if (this.game.reputation.value < -30 && dist < 15) { officer.state = 'chase'; this.game.notify('Полиция: "Стоять! Проверка документов!"', 'bad'); } } updateWarning(officer, dt) { officer.warnTimer -= dt; // Смотреть на игрока const dir = this.game.player.position.clone().sub(officer.position); officer.mesh.rotation.y = Math.atan2(dir.x, dir.z); if (officer.warnTimer <= 0) { // Если всё ещё попрошайничает — переход в chase if (this.game.player.isBegging) { officer.state = 'chase'; this.game.notify('Полиция: "Я предупреждал!"', 'bad'); } else { officer.state = 'patrol'; officer.warningGiven = false; } } } updateChase(officer, dt) { const playerPos = this.game.player.position; const dir = playerPos.clone().sub(officer.position); const dist = dir.length(); if (dist < 2) { // Поймал officer.state = 'fine'; this.showFineDialog(officer); return; } if (dist > 50) { // Потерял officer.state = 'patrol'; this.game.notify('Полиция потеряла вас из виду.'); return; } dir.normalize(); officer.position.add(dir.multiplyScalar(officer.chaseSpeed * dt)); officer.mesh.position.copy(officer.position); officer.mesh.rotation.y = Math.atan2(dir.x, dir.z); } showFineDialog(officer) { this.game.player.stopBegging(); this.game.player.stopBusking(); const fineAmount = 50 + Math.floor(Math.random() * 50); this.game.ui.showDialog('Полицейский', `Нарушение общественного порядка. Штраф ${fineAmount} ₽.`, [ `Заплатить штраф (${fineAmount} ₽)`, 'Нет денег...' ], (index) => { if (index === 0) { if (this.game.player.stats.money >= fineAmount) { this.game.player.stats.money -= fineAmount; this.game.notify(`Оплачен штраф ${fineAmount} ₽.`, 'bad'); } else { this.game.notify('Не хватает денег. Конфискация предметов.', 'bad'); this.confiscateItem(); } } else { this.game.notify('Конфискация предметов.', 'bad'); this.confiscateItem(); } this.game.reputation.change(-10); officer.state = 'patrol'; this.game.ui.hideDialog(); }); } confiscateItem() { const inv = this.game.inventory; const items = Object.keys(inv.items).filter(k => inv.items[k] > 0); if (items.length > 0) { const item = items[Math.floor(Math.random() * items.length)]; const name = inv.itemData[item]?.name || item; inv.removeItem(item, 1); this.game.notify(`Конфисковано: ${name}`, 'bad'); } } getDetectRange() { const rep = this.game.reputation.value; if (rep >= 50) return 0; // Уважаемых не трогают if (rep >= 20) return 10; if (rep <= -50) return 25; if (rep <= -20) return 22; return 20; } reset() { this.officers.forEach(o => { if (o.mesh) this.game.scene.remove(o.mesh); }); this.officers = []; this.warningCooldown = 0; } }