Files
Hommie_RPG_Game/js/game/Police.js
Maxim Dolgolyov fb5f09212b Initial commit: 3D Hommie RPG game
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:09 +03:00

275 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}