1175 lines
48 KiB
JavaScript
1175 lines
48 KiB
JavaScript
import * as THREE from 'three';
|
||
|
||
export class Player {
|
||
constructor(game) {
|
||
this.game = game;
|
||
|
||
// Характеристики
|
||
this.stats = {
|
||
health: 100,
|
||
hunger: 100,
|
||
warmth: 100,
|
||
mood: 50,
|
||
money: 0,
|
||
hygiene: 100
|
||
};
|
||
|
||
// Физика
|
||
this.position = new THREE.Vector3(0, 0, 15);
|
||
this.velocity = new THREE.Vector3();
|
||
this.direction = new THREE.Vector3();
|
||
this.speed = 5;
|
||
this.sprintMultiplier = 1.7;
|
||
this.height = 1.7;
|
||
this.radius = 0.4;
|
||
|
||
// Mesh
|
||
this.mesh = null;
|
||
this.nearestInteractable = null;
|
||
|
||
// Таймеры
|
||
this.statTimer = 0;
|
||
this.isSleeping = false;
|
||
this.sleepTimer = 0;
|
||
this.isWarming = false;
|
||
this._shelterSleep = false;
|
||
|
||
// Begging
|
||
this.isBegging = false;
|
||
this.begTimer = 0;
|
||
this.begCooldown = 0;
|
||
this.begDuration = 3;
|
||
|
||
// Busking
|
||
this.isBusking = false;
|
||
this.buskTimer = 0;
|
||
this.buskCooldown = 0;
|
||
this.buskDuration = 5;
|
||
|
||
// Hygiene & Disease
|
||
this.isDiseased = false;
|
||
this.diseaseTimer = 0;
|
||
this.diseaseCheckTimer = 0;
|
||
this.washCooldown = 0;
|
||
|
||
// Addiction
|
||
this.addictionLevel = 0;
|
||
this.withdrawalTimer = 0;
|
||
this.lastDrinkTime = 0;
|
||
|
||
// Шаги
|
||
this.stepTimer = 0;
|
||
this.stepInterval = 0.45;
|
||
|
||
// Предыдущее здоровье
|
||
this._prevHealth = 100;
|
||
|
||
// Head bob
|
||
this.bobPhase = 0;
|
||
this.bobAmount = 0;
|
||
|
||
// Stamina
|
||
this.stamina = 100;
|
||
this.maxStamina = 100;
|
||
this.isSprinting = false;
|
||
|
||
// Сытость 100 таймер (для ачивки)
|
||
this._wellFedTimer = 0;
|
||
}
|
||
|
||
spawn() {
|
||
this.createMesh();
|
||
this.position.set(0, 0, 15);
|
||
this.mesh.position.copy(this.position);
|
||
this.game.camera.position.set(0, this.height, 15);
|
||
}
|
||
|
||
createMesh() {
|
||
const group = new THREE.Group();
|
||
|
||
const bodyGeo = new THREE.CylinderGeometry(0.3, 0.35, 1.0, 8);
|
||
const bodyMat = new THREE.MeshStandardMaterial({ color: 0x5c4033 });
|
||
const body = new THREE.Mesh(bodyGeo, bodyMat);
|
||
body.position.y = 0.8;
|
||
body.castShadow = true;
|
||
group.add(body);
|
||
|
||
const headGeo = new THREE.SphereGeometry(0.22, 8, 6);
|
||
const headMat = new THREE.MeshStandardMaterial({ color: 0xd4a574 });
|
||
const head = new THREE.Mesh(headGeo, headMat);
|
||
head.position.y = 1.5;
|
||
head.castShadow = true;
|
||
group.add(head);
|
||
|
||
const hatGeo = new THREE.CylinderGeometry(0.25, 0.28, 0.2, 8);
|
||
const hatMat = new THREE.MeshStandardMaterial({ color: 0x333355 });
|
||
const hat = new THREE.Mesh(hatGeo, hatMat);
|
||
hat.position.y = 1.7;
|
||
group.add(hat);
|
||
|
||
const armMat = new THREE.MeshStandardMaterial({ color: 0x5c4033 });
|
||
[-0.4, 0.4].forEach(side => {
|
||
const armGeo = new THREE.CylinderGeometry(0.08, 0.08, 0.7, 6);
|
||
const arm = new THREE.Mesh(armGeo, armMat);
|
||
arm.position.set(side, 0.7, 0);
|
||
arm.rotation.z = side > 0 ? -0.15 : 0.15;
|
||
arm.castShadow = true;
|
||
group.add(arm);
|
||
});
|
||
|
||
const legMat = new THREE.MeshStandardMaterial({ color: 0x333344 });
|
||
[-0.15, 0.15].forEach(side => {
|
||
const legGeo = new THREE.CylinderGeometry(0.1, 0.1, 0.6, 6);
|
||
const leg = new THREE.Mesh(legGeo, legMat);
|
||
leg.position.set(side, 0.3, 0);
|
||
leg.castShadow = true;
|
||
group.add(leg);
|
||
});
|
||
|
||
this.mesh = group;
|
||
group.traverse(child => {
|
||
if (child.isMesh) {
|
||
child.castShadow = true;
|
||
child.layers.set(1);
|
||
}
|
||
});
|
||
this.game.scene.add(group);
|
||
}
|
||
|
||
reset() {
|
||
if (this.mesh) {
|
||
this.game.scene.remove(this.mesh);
|
||
}
|
||
this.stats = { health: 100, hunger: 100, warmth: 100, mood: 50, money: 0, hygiene: 100 };
|
||
this.stamina = 100;
|
||
this.isSleeping = false;
|
||
this.isWarming = false;
|
||
this.isBegging = false;
|
||
this.begTimer = 0;
|
||
this.begCooldown = 0;
|
||
this.isBusking = false;
|
||
this.buskTimer = 0;
|
||
this.buskCooldown = 0;
|
||
this.isDiseased = false;
|
||
this.diseaseTimer = 0;
|
||
this.diseaseCheckTimer = 0;
|
||
this.washCooldown = 0;
|
||
this.addictionLevel = 0;
|
||
this.withdrawalTimer = 0;
|
||
this.lastDrinkTime = 0;
|
||
this.nearestInteractable = null;
|
||
this._prevHealth = 100;
|
||
this._shelterSleep = false;
|
||
this._wellFedTimer = 0;
|
||
}
|
||
|
||
update(dt) {
|
||
if (this.isSleeping) {
|
||
this.updateSleep(dt);
|
||
this.updateScreenEffects();
|
||
return;
|
||
}
|
||
|
||
this.updateMovement(dt);
|
||
this.updateStats(dt);
|
||
this.updateBegging(dt);
|
||
this.updateBusking(dt);
|
||
this.checkInteractables();
|
||
this.updateScreenEffects();
|
||
this.checkDeath();
|
||
this.trackLocations();
|
||
|
||
if (this.begCooldown > 0) this.begCooldown -= dt;
|
||
if (this.buskCooldown > 0) this.buskCooldown -= dt;
|
||
}
|
||
|
||
updateMovement(dt) {
|
||
const keys = this.game.keys;
|
||
const camera = this.game.camera;
|
||
|
||
if (this.isBegging || this.isBusking) return;
|
||
|
||
const forward = new THREE.Vector3();
|
||
camera.getWorldDirection(forward);
|
||
forward.y = 0;
|
||
forward.normalize();
|
||
|
||
const right = new THREE.Vector3();
|
||
right.crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();
|
||
|
||
this.direction.set(0, 0, 0);
|
||
if (keys['KeyW']) this.direction.add(forward);
|
||
if (keys['KeyS']) this.direction.sub(forward);
|
||
if (keys['KeyD']) this.direction.add(right);
|
||
if (keys['KeyA']) this.direction.sub(right);
|
||
|
||
if (this.direction.length() > 0) {
|
||
this.direction.normalize();
|
||
|
||
const wantSprint = keys['ShiftLeft'] || keys['ShiftRight'];
|
||
const sprint = wantSprint && this.stamina > 0;
|
||
this.isSprinting = sprint;
|
||
|
||
if (sprint) {
|
||
this.stamina = Math.max(0, this.stamina - 20 * dt);
|
||
} else {
|
||
this.stamina = Math.min(this.maxStamina, this.stamina + 10 * dt);
|
||
}
|
||
|
||
const moveSpeed = this.speed * (sprint ? this.sprintMultiplier : 1);
|
||
|
||
let speedMod = 1;
|
||
if (this.stats.hunger < 20) speedMod *= 0.7;
|
||
if (this.stats.warmth < 15) speedMod *= 0.8;
|
||
if (this.stats.health < 25) speedMod *= 0.6;
|
||
if (this.isDiseased) speedMod *= 0.8;
|
||
|
||
const newPos = this.position.clone().add(
|
||
this.direction.clone().multiplyScalar(moveSpeed * speedMod * dt)
|
||
);
|
||
|
||
if (!this.checkCollision(newPos)) {
|
||
this.position.copy(newPos);
|
||
}
|
||
|
||
if (this.game.interiors && this.game.interiors.isInside) {
|
||
this.position.x = THREE.MathUtils.clamp(this.position.x, 490, 510);
|
||
this.position.z = THREE.MathUtils.clamp(this.position.z, -10, 120);
|
||
} else {
|
||
this.position.x = THREE.MathUtils.clamp(this.position.x, -95, 95);
|
||
this.position.z = THREE.MathUtils.clamp(this.position.z, -95, 95);
|
||
}
|
||
|
||
this.stepTimer += dt * (sprint ? 1.5 : 1);
|
||
if (this.stepTimer >= this.stepInterval) {
|
||
this.stepTimer = 0;
|
||
this.game.sound.playStep();
|
||
}
|
||
|
||
this.bobPhase += dt * (sprint ? 14 : 10);
|
||
this.bobAmount = THREE.MathUtils.lerp(this.bobAmount, 0.04, dt * 5);
|
||
} else {
|
||
this.bobAmount = THREE.MathUtils.lerp(this.bobAmount, 0, dt * 8);
|
||
this.isSprinting = false;
|
||
this.stamina = Math.min(this.maxStamina, this.stamina + 15 * dt);
|
||
}
|
||
|
||
this.mesh.position.copy(this.position);
|
||
const bobY = Math.sin(this.bobPhase) * this.bobAmount;
|
||
|
||
// Withdrawal tremor
|
||
let tremorX = 0, tremorY = 0;
|
||
if (this.addictionLevel > 40 && this.withdrawalTimer > 0) {
|
||
const intensity = Math.min(0.02, this.addictionLevel * 0.0003);
|
||
tremorX = (Math.random() - 0.5) * intensity;
|
||
tremorY = (Math.random() - 0.5) * intensity;
|
||
}
|
||
|
||
this.game.camera.position.set(
|
||
this.position.x + tremorX,
|
||
this.position.y + this.height + bobY + tremorY,
|
||
this.position.z
|
||
);
|
||
|
||
const camDir = new THREE.Vector3();
|
||
camera.getWorldDirection(camDir);
|
||
this.mesh.rotation.y = Math.atan2(camDir.x, camDir.z);
|
||
}
|
||
|
||
checkCollision(newPos) {
|
||
const playerBox = new THREE.Box3(
|
||
new THREE.Vector3(newPos.x - this.radius, 0, newPos.z - this.radius),
|
||
new THREE.Vector3(newPos.x + this.radius, this.height, newPos.z + this.radius)
|
||
);
|
||
|
||
// Статические коллайдеры (здания, мебель и т.д.)
|
||
for (const collider of this.game.world.colliders) {
|
||
if (playerBox.intersectsBox(collider)) return true;
|
||
}
|
||
|
||
// Динамические коллайдеры движущихся машин
|
||
for (const collider of this.game.world.vehicleColliders) {
|
||
if (!collider.isEmpty() && playerBox.intersectsBox(collider)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
updateStats(dt) {
|
||
this.statTimer += dt;
|
||
if (this.statTimer < 1) return;
|
||
this.statTimer = 0;
|
||
|
||
this._prevHealth = this.stats.health;
|
||
|
||
// Голод
|
||
const survMod = this.game.skills.getSurvivalModifier();
|
||
const hungerDrain = this.game.seasons.getHungerDrain();
|
||
const diseaseMult = this.isDiseased ? 1.5 : 1;
|
||
this.stats.hunger = Math.max(0, this.stats.hunger - 0.15 * survMod * hungerDrain * diseaseMult);
|
||
|
||
// Тепло (с бонусом экипировки)
|
||
const warmthDrain = this.game.seasons.getWarmthDrain();
|
||
const eqWarmth = this.game.equipment.getWarmthBonus();
|
||
const warmthReduction = Math.min(0.8, eqWarmth * 0.01); // Макс 80% снижение потери
|
||
|
||
if (this.game.isNight()) {
|
||
const drain = this.isWarming ? 0 : 0.3 * warmthDrain * (1 - warmthReduction);
|
||
this.stats.warmth = Math.max(0, this.stats.warmth - drain);
|
||
|
||
// В своём укрытии с крышей — меньше потеря
|
||
if (this.game.housing.isPlayerInShelter() && this.game.housing.hasRoof()) {
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + 0.1);
|
||
}
|
||
} else {
|
||
const warmthRegen = this.game.seasons.current === 'summer' ? 0.1 : 0.05;
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + warmthRegen);
|
||
}
|
||
|
||
if (this.isWarming) {
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + 0.5);
|
||
}
|
||
|
||
// Настроение от экипировки
|
||
const eqMood = this.game.equipment.getMoodBonus();
|
||
if (eqMood > 0) {
|
||
this.stats.mood = Math.min(100, this.stats.mood + eqMood * 0.02);
|
||
}
|
||
|
||
// Здоровье
|
||
if (this.stats.hunger <= 0) {
|
||
this.stats.health = Math.max(0, this.stats.health - 0.5);
|
||
}
|
||
if (this.stats.warmth <= 10) {
|
||
this.stats.health = Math.max(0, this.stats.health - 0.3);
|
||
}
|
||
if (this.stats.hunger > 50 && this.stats.warmth > 50) {
|
||
this.stats.health = Math.min(100, this.stats.health + 0.1);
|
||
}
|
||
|
||
// Гигиена
|
||
let hygieneDrain = 0.03;
|
||
if (this.game.weather.current === 'rain') hygieneDrain += 0.02;
|
||
this.stats.hygiene = Math.max(0, this.stats.hygiene - hygieneDrain);
|
||
|
||
// Болезнь
|
||
if (this.isDiseased) {
|
||
this.stats.health = Math.max(0, this.stats.health - 0.1);
|
||
this.diseaseTimer--;
|
||
if (this.diseaseTimer <= 0) {
|
||
this.isDiseased = false;
|
||
this.game.notify('Вы выздоровели!', 'good');
|
||
}
|
||
} else if (this.stats.hygiene < 20) {
|
||
this.diseaseCheckTimer++;
|
||
if (this.diseaseCheckTimer >= 60) {
|
||
this.diseaseCheckTimer = 0;
|
||
if (Math.random() < 0.3) {
|
||
this.isDiseased = true;
|
||
this.diseaseTimer = 180;
|
||
this.game.notify('Вы заболели! Нужна аптечка или больница.', 'bad');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (this.washCooldown > 0) this.washCooldown--;
|
||
|
||
// Зависимость
|
||
if (this.addictionLevel > 0) {
|
||
this.addictionLevel = Math.max(0, this.addictionLevel - 0.008);
|
||
}
|
||
if (this.addictionLevel > 40) {
|
||
const currentTime = this.game.gameTime + (this.game.gameDay - 1) * 24 * 60;
|
||
const timeSinceDrink = currentTime - this.lastDrinkTime;
|
||
if (timeSinceDrink > 30) {
|
||
// Ломка
|
||
this.withdrawalTimer++;
|
||
if (this.withdrawalTimer % 60 === 0) {
|
||
this.stats.mood = Math.max(0, this.stats.mood - 5);
|
||
this.game.notify('Хочется выпить...', 'bad');
|
||
}
|
||
}
|
||
}
|
||
// Квест: снизить зависимость до 0
|
||
if (this.addictionLevel <= 0 && this._hadAddiction) {
|
||
this.game.questSystem.onEvent('sobriety');
|
||
this._hadAddiction = false;
|
||
}
|
||
if (this.addictionLevel >= 50) this._hadAddiction = true;
|
||
|
||
// Настроение
|
||
if (this.stats.hunger < 20) this.stats.mood = Math.max(0, this.stats.mood - 0.2);
|
||
if (this.stats.warmth < 20) this.stats.mood = Math.max(0, this.stats.mood - 0.15);
|
||
if (this.stats.health < 30) this.stats.mood = Math.max(0, this.stats.mood - 0.1);
|
||
if (this.stats.hygiene < 30) this.stats.mood = Math.max(0, this.stats.mood - 0.1);
|
||
|
||
// Звук урона
|
||
if (this.stats.health < this._prevHealth - 1) {
|
||
this.game.sound.playHurt();
|
||
}
|
||
|
||
// Ачивка: сытость 100
|
||
if (this.stats.hunger >= 99) {
|
||
this._wellFedTimer++;
|
||
if (this._wellFedTimer >= 300) {
|
||
this.game.achievements.check('well_fed');
|
||
}
|
||
} else {
|
||
this._wellFedTimer = 0;
|
||
}
|
||
}
|
||
|
||
updateScreenEffects() {
|
||
const dmg = document.getElementById('effect-damage');
|
||
const cold = document.getElementById('effect-cold');
|
||
const hunger = document.getElementById('effect-hunger');
|
||
const sleep = document.getElementById('effect-sleep');
|
||
|
||
if (this.stats.health < this._prevHealth - 1) {
|
||
dmg.classList.add('active');
|
||
setTimeout(() => dmg.classList.remove('active'), 400);
|
||
}
|
||
|
||
if (this.stats.warmth < 25) {
|
||
cold.classList.add('active');
|
||
} else {
|
||
cold.classList.remove('active');
|
||
}
|
||
|
||
if (this.stats.hunger < 15) {
|
||
hunger.classList.add('active');
|
||
} else {
|
||
hunger.classList.remove('active');
|
||
}
|
||
|
||
if (this.isSleeping) {
|
||
sleep.classList.add('active');
|
||
} else {
|
||
sleep.classList.remove('active');
|
||
}
|
||
|
||
const disease = document.getElementById('effect-disease');
|
||
if (disease) {
|
||
if (this.isDiseased) {
|
||
disease.classList.add('active');
|
||
} else {
|
||
disease.classList.remove('active');
|
||
}
|
||
}
|
||
}
|
||
|
||
checkInteractables() {
|
||
let nearest = null;
|
||
let nearestDist = Infinity;
|
||
|
||
for (const obj of this.game.world.interactables) {
|
||
const dist = this.position.distanceTo(obj.position);
|
||
if (dist < obj.radius && dist < nearestDist) {
|
||
nearest = obj;
|
||
nearestDist = dist;
|
||
}
|
||
}
|
||
|
||
// NPC
|
||
for (const npc of this.game.npcManager.npcs) {
|
||
const dist = this.position.distanceTo(npc.position);
|
||
if (dist < 3 && dist < nearestDist) {
|
||
nearest = {
|
||
type: 'npc',
|
||
npc: npc,
|
||
position: npc.position,
|
||
label: `Поговорить: ${npc.name}`
|
||
};
|
||
nearestDist = dist;
|
||
}
|
||
}
|
||
|
||
// Костёр
|
||
this.isWarming = false;
|
||
for (const obj of this.game.world.interactables) {
|
||
if (obj.type === 'campfire') {
|
||
const dist = this.position.distanceTo(obj.position);
|
||
if (dist < obj.radius) {
|
||
this.isWarming = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Печка в укрытии
|
||
if (this.game.housing.built && this.game.housing.upgrades.stove.built) {
|
||
if (this.position.distanceTo(this.game.housing.position) < 5) {
|
||
this.isWarming = true;
|
||
}
|
||
}
|
||
|
||
this.nearestInteractable = nearest;
|
||
this.game.ui.updateInteractionHint(nearest);
|
||
}
|
||
|
||
interact() {
|
||
if (!this.nearestInteractable) return;
|
||
const obj = this.nearestInteractable;
|
||
|
||
switch (obj.type) {
|
||
case 'dumpster':
|
||
this.searchDumpster(obj);
|
||
break;
|
||
case 'bench':
|
||
this.restOnBench();
|
||
break;
|
||
case 'shop':
|
||
this.game.interiors.enterBuilding('shop');
|
||
break;
|
||
case 'shelter':
|
||
this.sleep();
|
||
break;
|
||
case 'campfire':
|
||
this.game.notify('Вы греетесь у костра...');
|
||
break;
|
||
case 'npc':
|
||
this.game.npcManager.talkTo(obj.npc);
|
||
break;
|
||
case 'fountain':
|
||
this.drinkFountain();
|
||
break;
|
||
case 'trashpile':
|
||
this.searchTrashPile(obj);
|
||
break;
|
||
case 'phone':
|
||
this.usePhone();
|
||
break;
|
||
case 'church':
|
||
this.game.interiors.enterBuilding('church');
|
||
break;
|
||
case 'jobboard':
|
||
this.game.jobSystem.showJobBoard();
|
||
break;
|
||
case 'player_shelter':
|
||
this.game.housing.showMenu();
|
||
break;
|
||
case 'camp_spot':
|
||
this.game.housing.showMenu();
|
||
break;
|
||
case 'hospital':
|
||
this.game.interiors.enterBuilding('hospital');
|
||
break;
|
||
case 'market':
|
||
this.enterMarket();
|
||
break;
|
||
// Интерьерные объекты
|
||
case 'shop_counter':
|
||
this.enterShop();
|
||
break;
|
||
case 'hospital_desk':
|
||
this.enterHospital();
|
||
break;
|
||
case 'church_altar':
|
||
this.enterChurch();
|
||
break;
|
||
case 'exit_door':
|
||
this.game.interiors.exitBuilding();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// === Попрошайничество ===
|
||
startBegging() {
|
||
if (this.isBegging || this.begCooldown > 0) return;
|
||
|
||
let nearNPC = false;
|
||
for (const npc of this.game.npcManager.npcs) {
|
||
if (this.position.distanceTo(npc.position) < 8) {
|
||
nearNPC = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!nearNPC) {
|
||
this.game.notify('Рядом никого нет...');
|
||
return;
|
||
}
|
||
|
||
this.isBegging = true;
|
||
this.begTimer = 0;
|
||
this.game.ui.showBegProgress(true);
|
||
}
|
||
|
||
stopBegging() {
|
||
if (!this.isBegging) return;
|
||
this.isBegging = false;
|
||
this.game.ui.showBegProgress(false);
|
||
}
|
||
|
||
updateBegging(dt) {
|
||
if (!this.isBegging) return;
|
||
|
||
this.begTimer += dt;
|
||
this.game.ui.updateBegProgress(this.begTimer / this.begDuration);
|
||
|
||
if (this.begTimer >= this.begDuration) {
|
||
this.isBegging = false;
|
||
this.begCooldown = 15;
|
||
this.game.ui.showBegProgress(false);
|
||
|
||
const begBonus = this.game.skills.getBegBonus() * this.game.reputation.getBegModifier();
|
||
this.game.skills.addXP('begging', 2);
|
||
this.game.reputation.change(-1);
|
||
const roll = Math.random();
|
||
if (roll < 0.35) {
|
||
const amount = Math.floor((Math.random() * 20 + 5) * begBonus);
|
||
this.stats.money += amount;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Вам дали ${amount} ₽!`, 'good');
|
||
this.game.questSystem.onEvent('find_money', amount);
|
||
this.game.questSystem.onEvent('beg');
|
||
} else if (roll < 0.55) {
|
||
this.game.inventory.addItem('bread', 1);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Вам дали хлеб!', 'good');
|
||
this.game.questSystem.onEvent('find_food');
|
||
} else if (roll < 0.7) {
|
||
this.game.notify('Прохожие игнорируют вас...');
|
||
this.stats.mood = Math.max(0, this.stats.mood - 3);
|
||
} else if (roll < 0.85) {
|
||
this.game.notify('"Иди работай!" — грубо ответили вам.');
|
||
this.stats.mood = Math.max(0, this.stats.mood - 5);
|
||
} else {
|
||
const amount = Math.floor(Math.random() * 50) + 20;
|
||
this.stats.money += amount;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Щедрый прохожий дал ${amount} ₽!`, 'good');
|
||
this.game.questSystem.onEvent('find_money', amount);
|
||
this.stats.mood = Math.min(100, this.stats.mood + 5);
|
||
}
|
||
}
|
||
}
|
||
|
||
// === Бускинг ===
|
||
startBusking() {
|
||
if (this.isBusking || this.buskCooldown > 0 || this.isBegging) return;
|
||
|
||
if (this.game.inventory.getCount('harmonica') <= 0) {
|
||
this.game.notify('У вас нет гармошки!');
|
||
return;
|
||
}
|
||
|
||
// Ищем прохожих и NPC в радиусе 12
|
||
let nearCount = 0;
|
||
for (const npc of this.game.npcManager.npcs) {
|
||
if (this.position.distanceTo(npc.position) < 12) nearCount++;
|
||
}
|
||
if (this.game.npcManager.passersby) {
|
||
for (const pb of this.game.npcManager.passersby) {
|
||
if (pb.mesh) {
|
||
const dist = this.position.distanceTo(pb.mesh.position);
|
||
if (dist < 12) nearCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (nearCount === 0) {
|
||
this.game.notify('Рядом некому играть...');
|
||
return;
|
||
}
|
||
|
||
this.isBusking = true;
|
||
this.buskTimer = 0;
|
||
this._buskNearCount = nearCount;
|
||
this.game.ui.showBuskProgress(true);
|
||
}
|
||
|
||
stopBusking() {
|
||
if (!this.isBusking) return;
|
||
this.isBusking = false;
|
||
this.game.ui.showBuskProgress(false);
|
||
}
|
||
|
||
updateBusking(dt) {
|
||
if (!this.isBusking) return;
|
||
|
||
this.buskTimer += dt;
|
||
this.game.ui.updateBuskProgress(this.buskTimer / this.buskDuration);
|
||
|
||
if (this.buskTimer >= this.buskDuration) {
|
||
this.isBusking = false;
|
||
this.buskCooldown = 20;
|
||
this.game.ui.showBuskProgress(false);
|
||
|
||
const begBonus = this.game.skills.getBegBonus();
|
||
const crowdMult = Math.min(2, 1 + (this._buskNearCount - 1) * 0.3);
|
||
this.game.skills.addXP('begging', 1);
|
||
|
||
const roll = Math.random();
|
||
if (roll < 0.50) {
|
||
// Дали денег
|
||
const amount = Math.floor((Math.random() * 20 + 10) * begBonus * crowdMult);
|
||
this.stats.money += amount;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Прохожие оценили игру! +${amount} ₽`, 'good');
|
||
this.game.questSystem.onEvent('find_money', amount);
|
||
this.game.questSystem.onEvent('busking');
|
||
} else if (roll < 0.75) {
|
||
// Хорошие деньги
|
||
const amount = Math.floor((Math.random() * 30 + 20) * begBonus * crowdMult);
|
||
this.stats.money += amount;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Отличное выступление! +${amount} ₽`, 'good');
|
||
this.game.questSystem.onEvent('find_money', amount);
|
||
this.game.questSystem.onEvent('busking');
|
||
this.stats.mood = Math.min(100, this.stats.mood + 5);
|
||
} else if (roll < 0.90) {
|
||
// Никто не обратил внимания
|
||
this.game.notify('Прохожие не обратили внимания...');
|
||
} else {
|
||
// Бонус к настроению
|
||
this.stats.mood = Math.min(100, this.stats.mood + 10);
|
||
const amount = Math.floor((Math.random() * 15 + 5) * begBonus * crowdMult);
|
||
this.stats.money += amount;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Музыка подняла настроение! +${amount} ₽, +10 Настроение`, 'good');
|
||
this.game.questSystem.onEvent('find_money', amount);
|
||
this.game.questSystem.onEvent('busking');
|
||
}
|
||
}
|
||
}
|
||
|
||
// === Взаимодействия ===
|
||
searchDumpster(dumpster) {
|
||
if (dumpster.searchCooldown > 0) {
|
||
this.game.notify('Тут уже нечего искать. Подождите...');
|
||
return;
|
||
}
|
||
|
||
const scavBonus = this.game.skills.getScavengeBonus();
|
||
dumpster.searchCooldown = Math.floor(120 / scavBonus);
|
||
this.game.sound.playPickup();
|
||
this.game.skills.addXP('scavenging', 1);
|
||
this.stats.hygiene = Math.max(0, this.stats.hygiene - 5);
|
||
|
||
const roll = Math.random();
|
||
if (roll < 0.18) {
|
||
const count = Math.random() < 0.3 + scavBonus * 0.05 ? 2 : 1;
|
||
this.game.inventory.addItem('bottle', count);
|
||
this.game.notify(`Вы нашли бутылк${count > 1 ? 'и' : 'у'}!`, 'good');
|
||
this.game.questSystem.onEvent('collect_bottle');
|
||
if (count > 1) this.game.questSystem.onEvent('collect_bottle');
|
||
} else if (roll < 0.29) {
|
||
this.game.inventory.addItem('bread', 1);
|
||
this.game.notify('Вы нашли кусок хлеба!', 'good');
|
||
this.game.questSystem.onEvent('find_food');
|
||
} else if (roll < 0.38) {
|
||
this.game.inventory.addItem('can', 1);
|
||
this.game.notify('Вы нашли консервную банку!', 'good');
|
||
this.game.questSystem.onEvent('find_food');
|
||
} else if (roll < 0.47) {
|
||
this.game.inventory.addItem('clothing', 1);
|
||
this.game.notify('Вы нашли старую одежду!', 'good');
|
||
} else if (roll < 0.54) {
|
||
this.game.inventory.addItem('newspaper', 1);
|
||
this.game.notify('Вы нашли газету!');
|
||
} else if (roll < 0.62) {
|
||
const coins = Math.floor(Math.random() * 15) + 1;
|
||
this.stats.money += coins;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Вы нашли ${coins} ₽!`, 'good');
|
||
this.game.questSystem.onEvent('find_money', coins);
|
||
} else if (roll < 0.70) {
|
||
this.game.inventory.addItem('scrap', 1);
|
||
this.game.notify('Вы нашли полезный хлам!', 'good');
|
||
} else if (roll < 0.77) {
|
||
this.game.inventory.addItem('rope', 1);
|
||
this.game.notify('Вы нашли верёвку!', 'good');
|
||
} else if (roll < 0.82) {
|
||
// Шанс найти экипировку
|
||
const eqItems = ['eq_old_hat', 'eq_old_jacket', 'eq_old_boots', 'eq_old_gloves'];
|
||
const eqItem = eqItems[Math.floor(Math.random() * eqItems.length)];
|
||
this.game.inventory.addItem(eqItem, 1);
|
||
this.game.notify(`Вы нашли ${this.game.inventory.itemData[eqItem].name}!`, 'good');
|
||
} else if (roll < 0.87) {
|
||
this.game.inventory.addItem('apple', 1);
|
||
this.game.notify('Вы нашли яблоко!', 'good');
|
||
} else {
|
||
this.game.notify('Ничего полезного...');
|
||
this.stats.mood = Math.max(0, this.stats.mood - 3);
|
||
}
|
||
}
|
||
|
||
restOnBench() {
|
||
this.stats.mood = Math.min(100, this.stats.mood + 5);
|
||
this.stats.health = Math.min(100, this.stats.health + 2);
|
||
this.game.notify('Вы отдохнули на скамейке. +5 Настроение, +2 Здоровье.', 'good');
|
||
}
|
||
|
||
drinkFountain() {
|
||
this.game.ui.showDialog('Фонтанчик', 'Что хотите сделать?', [
|
||
'Попить воды',
|
||
'Помыться',
|
||
'Уйти'
|
||
], (index) => {
|
||
if (index === 0) {
|
||
this.stats.hunger = Math.min(100, this.stats.hunger + 5);
|
||
this.stats.mood = Math.min(100, this.stats.mood + 2);
|
||
this.game.notify('Вы попили воды. +5 Сытость, +2 Настроение', 'good');
|
||
} else if (index === 1) {
|
||
if (this.washCooldown > 0) {
|
||
this.game.notify('Вы недавно мылись. Подождите...');
|
||
} else {
|
||
this.stats.hygiene = Math.min(100, this.stats.hygiene + 40);
|
||
this.washCooldown = 300;
|
||
this.game.notify('+40 Гигиена', 'good');
|
||
this.game.questSystem.onEvent('wash');
|
||
}
|
||
}
|
||
this.game.ui.hideDialog();
|
||
});
|
||
}
|
||
|
||
searchTrashPile(obj) {
|
||
if (obj.searchCooldown > 0) {
|
||
this.game.notify('Здесь уже нечего искать...');
|
||
return;
|
||
}
|
||
obj.searchCooldown = 90;
|
||
this.game.skills.addXP('scavenging', 1);
|
||
this.stats.hygiene = Math.max(0, this.stats.hygiene - 3);
|
||
|
||
const roll = Math.random();
|
||
if (roll < 0.25) {
|
||
this.game.inventory.addItem('bottle', 1);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Нашли бутылку в мусоре!', 'good');
|
||
this.game.questSystem.onEvent('collect_bottle');
|
||
} else if (roll < 0.38) {
|
||
const coins = Math.floor(Math.random() * 5) + 1;
|
||
this.stats.money += coins;
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Нашли ${coins} ₽!`, 'good');
|
||
} else if (roll < 0.50) {
|
||
this.game.inventory.addItem('scrap', 1);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Нашли полезный хлам!', 'good');
|
||
} else if (roll < 0.60) {
|
||
this.game.inventory.addItem('candle', 1);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Нашли свечу!', 'good');
|
||
} else if (roll < 0.68) {
|
||
this.game.inventory.addItem('rope', 1);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Нашли верёвку!', 'good');
|
||
} else {
|
||
this.game.notify('Мусор как мусор...');
|
||
}
|
||
}
|
||
|
||
usePhone() {
|
||
this.game.ui.showDialog('Таксофон', 'Позвонить на горячую линию помощи?', [
|
||
'Позвонить (бесплатно)',
|
||
'Не нужно'
|
||
], (index) => {
|
||
if (index === 0) {
|
||
this.stats.mood = Math.min(100, this.stats.mood + 10);
|
||
this.game.notify('Вам дали полезные советы. +10 Настроение', 'good');
|
||
}
|
||
this.game.ui.hideDialog();
|
||
});
|
||
}
|
||
|
||
enterShop() {
|
||
const items = [
|
||
{ name: 'Хлеб', key: 'bread', price: 30, desc: '+20 Сытость' },
|
||
{ name: 'Консервы', key: 'can', price: 50, desc: '+35 Сытость' },
|
||
{ name: 'Чай', key: 'tea', price: 20, desc: '+20 Тепло, +5 Настроение' },
|
||
{ name: 'Бинт', key: 'bandage', price: 40, desc: '+25 Здоровье' },
|
||
{ name: 'Свеча', key: 'candle', price: 15, desc: 'Для крафта' },
|
||
{ name: 'Верёвка', key: 'rope', price: 25, desc: 'Для крафта' },
|
||
{ name: 'Яблоко', key: 'apple', price: 15, desc: '+10 Сытость, +5 Здоровье' },
|
||
{ name: 'Мыло', key: 'soap', price: 20, desc: '+30 Гигиена' },
|
||
{ name: 'Перчатки', key: 'eq_gloves', price: 60, desc: 'Экипировка: +8 Тепло' },
|
||
{ name: 'Ботинки', key: 'eq_boots', price: 80, desc: 'Экипировка: +10 Тепло' },
|
||
];
|
||
|
||
const discount = this.game.skills.getTradeDiscount() * this.game.reputation.getShopModifier();
|
||
const choices = items.map(item => {
|
||
const finalPrice = Math.floor(item.price * discount);
|
||
return `${item.name} — ${finalPrice} ₽ (${item.desc})`;
|
||
});
|
||
choices.push('Сдать бутылки (5₽/шт)');
|
||
choices.push('Уйти');
|
||
|
||
this.game.sound.playDialogOpen();
|
||
|
||
this.game.ui.showDialog('Продавец', 'Чего желаете?', choices, (index) => {
|
||
if (index < items.length) {
|
||
const item = items[index];
|
||
const finalPrice = Math.floor(item.price * discount);
|
||
if (this.stats.money >= finalPrice) {
|
||
this.stats.money -= finalPrice;
|
||
this.game.skills.addXP('trading', 1);
|
||
this.game.inventory.addItem(item.key, 1);
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Куплено: ${item.name}`, 'good');
|
||
} else {
|
||
this.game.notify('Не хватает денег!', 'bad');
|
||
}
|
||
} else if (index === items.length) {
|
||
const bottles = this.game.inventory.getCount('bottle');
|
||
if (bottles > 0) {
|
||
const earnings = bottles * 5;
|
||
this.stats.money += earnings;
|
||
this.game.inventory.removeItem('bottle', bottles);
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Сдали ${bottles} бутылок за ${earnings} ₽!`, 'good');
|
||
this.game.questSystem.onEvent('sell_bottles', bottles);
|
||
this.game.totalBottlesSold += bottles;
|
||
if (this.game.totalBottlesSold >= 20) {
|
||
this.game.achievements.check('bottle_king');
|
||
}
|
||
} else {
|
||
this.game.notify('У вас нет бутылок.');
|
||
}
|
||
}
|
||
this.game.ui.hideDialog();
|
||
});
|
||
}
|
||
|
||
enterHospital() {
|
||
const choices = ['Осмотр (бесплатно)', 'Лечение (50₽)', 'Уйти'];
|
||
|
||
this.game.sound.playDialogOpen();
|
||
this.game.ui.showDialog('Больница', 'Добро пожаловать. Чем помочь?', choices, (index) => {
|
||
if (index === 0) {
|
||
this.stats.health = Math.min(100, this.stats.health + 15);
|
||
if (this.isDiseased) {
|
||
this.isDiseased = false;
|
||
this.diseaseTimer = 0;
|
||
this.game.notify('+15 Здоровье. Болезнь вылечена!', 'good');
|
||
} else {
|
||
this.game.notify('+15 Здоровье. Вас осмотрели.', 'good');
|
||
}
|
||
} else if (index === 1) {
|
||
if (this.stats.money >= 50) {
|
||
this.stats.money -= 50;
|
||
this.stats.health = 100;
|
||
this.isDiseased = false;
|
||
this.diseaseTimer = 0;
|
||
this.game.sound.playCoin();
|
||
this.game.notify('Полное здоровье восстановлено! Болезнь вылечена!', 'good');
|
||
} else {
|
||
this.game.notify('Не хватает денег!', 'bad');
|
||
}
|
||
}
|
||
this.game.ui.hideDialog();
|
||
this.game.questSystem.onEvent('visit_hospital');
|
||
});
|
||
}
|
||
|
||
enterMarket() {
|
||
const items = [
|
||
{ name: 'Рыба', key: 'fish', price: 25, desc: '+25 Сытость' },
|
||
{ name: 'Пальто', key: 'eq_coat', price: 120, desc: 'Экипировка: +18 Тепло' },
|
||
{ name: 'Тёплые сапоги', key: 'eq_warm_boots', price: 100, desc: 'Экипировка: +18 Тепло' },
|
||
{ name: 'Жилетка', key: 'eq_vest', price: 150, desc: 'Экипировка: +10 Защита' },
|
||
{ name: 'Каска', key: 'eq_helmet', price: 90, desc: 'Экипировка: +8 Защита' },
|
||
{ name: 'Водка', key: 'vodka', price: 40, desc: '+30 Тепло, -10 Здоровье' },
|
||
];
|
||
|
||
const discount = this.game.skills.getTradeDiscount() * this.game.reputation.getShopModifier();
|
||
const choices = items.map(item => {
|
||
const finalPrice = Math.floor(item.price * discount);
|
||
return `${item.name} — ${finalPrice} ₽ (${item.desc})`;
|
||
});
|
||
choices.push('Уйти');
|
||
|
||
this.game.sound.playDialogOpen();
|
||
this.game.ui.showDialog('Рынок', 'Здесь можно найти полезные вещи.', choices, (index) => {
|
||
if (index < items.length) {
|
||
const item = items[index];
|
||
const finalPrice = Math.floor(item.price * discount);
|
||
if (this.stats.money >= finalPrice) {
|
||
this.stats.money -= finalPrice;
|
||
this.game.skills.addXP('trading', 2);
|
||
this.game.inventory.addItem(item.key, 1);
|
||
this.game.sound.playCoin();
|
||
this.game.notify(`Куплено: ${item.name}`, 'good');
|
||
} else {
|
||
this.game.notify('Не хватает денег!', 'bad');
|
||
}
|
||
}
|
||
this.game.ui.hideDialog();
|
||
});
|
||
}
|
||
|
||
sleep() {
|
||
if (this.stats.hunger < 10) {
|
||
this.game.notify('Слишком голодно, не уснуть...', 'bad');
|
||
return;
|
||
}
|
||
this.isSleeping = true;
|
||
this.sleepTimer = 0;
|
||
this._shelterSleep = false;
|
||
this.game.notify('Вы легли спать...');
|
||
}
|
||
|
||
updateSleep(dt) {
|
||
this.sleepTimer += dt;
|
||
if (this.sleepTimer >= 5) {
|
||
this.isSleeping = false;
|
||
|
||
let bonus;
|
||
if (this._shelterSleep) {
|
||
bonus = this.game.housing.getSleepBonus();
|
||
} else {
|
||
bonus = { health: 15, mood: 10, warmth: 20 };
|
||
}
|
||
|
||
this.stats.health = Math.min(100, this.stats.health + bonus.health);
|
||
this.stats.mood = Math.min(100, this.stats.mood + bonus.mood);
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + bonus.warmth);
|
||
this.stats.hunger = Math.max(0, this.stats.hunger - 15);
|
||
this.game.gameTime += 360;
|
||
this._shelterSleep = false;
|
||
|
||
const bonusText = this._shelterSleep ? ' (улучшенный отдых)' : '';
|
||
this.game.notify(`Вы проснулись отдохнувшим.${bonusText}`, 'good');
|
||
this.game.questSystem.onEvent('sleep');
|
||
}
|
||
}
|
||
|
||
checkDeath() {
|
||
if (this.stats.health <= 0) {
|
||
let reason = 'Вы не выдержали тяжёлой жизни на улице.';
|
||
if (this.stats.hunger <= 0) reason = 'Вы умерли от голода.';
|
||
if (this.stats.warmth <= 0) reason = 'Вы замёрзли насмерть.';
|
||
this.game.gameOver(reason);
|
||
}
|
||
}
|
||
|
||
useItem(itemKey) {
|
||
const effects = {
|
||
bread: () => { this.stats.hunger = Math.min(100, this.stats.hunger + 20); this.game.sound.playEat(); this.game.notify('+20 Сытость', 'good'); },
|
||
can: () => { this.stats.hunger = Math.min(100, this.stats.hunger + 35); this.game.sound.playEat(); this.game.notify('+35 Сытость', 'good'); },
|
||
tea: () => { this.stats.warmth = Math.min(100, this.stats.warmth + 20); this.stats.mood += 5; this.game.sound.playEat(); this.game.notify('+20 Тепло, +5 Настроение', 'good'); },
|
||
bandage: () => { this.stats.health = Math.min(100, this.stats.health + 25); this.game.notify('+25 Здоровье', 'good'); },
|
||
clothing: () => { this.stats.warmth = Math.min(100, this.stats.warmth + 30); this.game.notify('+30 Тепло', 'good'); },
|
||
newspaper: () => { this.stats.mood = Math.min(100, this.stats.mood + 5); this.game.notify('Вы почитали газету. +5 Настроение', 'good'); },
|
||
medkit: () => { this.stats.health = Math.min(100, this.stats.health + 50); if (this.isDiseased) { this.isDiseased = false; this.diseaseTimer = 0; this.game.notify('+50 Здоровье. Болезнь вылечена!', 'good'); } else { this.game.notify('+50 Здоровье', 'good'); } },
|
||
stew: () => { this.stats.hunger = Math.min(100, this.stats.hunger + 50); this.stats.warmth = Math.min(100, this.stats.warmth + 15); this.game.sound.playEat(); this.game.notify('+50 Сытость, +15 Тепло', 'good'); },
|
||
blanket: () => { this.stats.warmth = Math.min(100, this.stats.warmth + 50); this.stats.mood = Math.min(100, this.stats.mood + 10); this.game.notify('+50 Тепло, +10 Настроение', 'good'); },
|
||
soap: () => { this.stats.hygiene = Math.min(100, this.stats.hygiene + 30); this.game.notify('+30 Гигиена', 'good'); },
|
||
harmonica: () => {
|
||
// Если рядом есть люди — начать бускинг, иначе просто играть для себя
|
||
let hasNearby = false;
|
||
for (const npc of this.game.npcManager.npcs) {
|
||
if (this.position.distanceTo(npc.position) < 12) { hasNearby = true; break; }
|
||
}
|
||
if (!hasNearby && this.game.npcManager.passersby) {
|
||
for (const pb of this.game.npcManager.passersby) {
|
||
if (pb.mesh && this.position.distanceTo(pb.mesh.position) < 12) { hasNearby = true; break; }
|
||
}
|
||
}
|
||
if (hasNearby && this.buskCooldown <= 0 && !this.isBusking) {
|
||
this.startBusking();
|
||
} else {
|
||
this.stats.mood = Math.min(100, this.stats.mood + 20);
|
||
this.game.notify('Вы сыграли мелодию. +20 Настроение', 'good');
|
||
}
|
||
},
|
||
fish: () => { this.stats.hunger = Math.min(100, this.stats.hunger + 25); this.game.sound.playEat(); this.game.notify('+25 Сытость', 'good'); },
|
||
apple: () => { this.stats.hunger = Math.min(100, this.stats.hunger + 10); this.stats.health = Math.min(100, this.stats.health + 5); this.game.sound.playEat(); this.game.notify('+10 Сытость, +5 Здоровье', 'good'); },
|
||
vodka: () => {
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + (this.addictionLevel > 60 ? 40 : 30));
|
||
this.stats.health = Math.max(0, this.stats.health - 10);
|
||
const moodGain = this.addictionLevel > 80 ? 0 : 15;
|
||
if (moodGain > 0) this.stats.mood = Math.min(100, this.stats.mood + moodGain);
|
||
this.addictionLevel = Math.min(100, this.addictionLevel + 20);
|
||
this.lastDrinkTime = this.game.gameTime + (this.game.gameDay - 1) * 24 * 60;
|
||
this.withdrawalTimer = 0;
|
||
const msg = this.addictionLevel > 80
|
||
? '+40 Тепло, -10 Здоровье (привычка...)'
|
||
: this.addictionLevel > 60
|
||
? '+40 Тепло, -10 Здоровье, +15 Настроение (толерантность)'
|
||
: '+30 Тепло, -10 Здоровье, +15 Настроение';
|
||
this.game.notify(msg, 'good');
|
||
},
|
||
vitamins: () => { this.stats.health = Math.min(100, this.stats.health + 15); this.stats.mood = Math.min(100, this.stats.mood + 10); this.game.notify('+15 Здоровье, +10 Настроение', 'good'); },
|
||
torch: () => {
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + 20);
|
||
if (this.game.isNight()) {
|
||
this.stats.mood = Math.min(100, this.stats.mood + 10);
|
||
this.game.notify('+20 Тепло, +10 Настроение', 'good');
|
||
} else {
|
||
this.game.notify('+20 Тепло', 'good');
|
||
}
|
||
},
|
||
};
|
||
|
||
if (effects[itemKey]) {
|
||
effects[itemKey]();
|
||
if (itemKey !== 'harmonica') {
|
||
this.game.inventory.removeItem(itemKey, 1);
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
enterChurch() {
|
||
this.game.ui.showDialog('Церковь', 'Тёплое помещение. Тихо играет органная музыка. Добрая женщина предлагает помощь.', [
|
||
'Попросить еду',
|
||
'Попросить одежду',
|
||
'Помолиться',
|
||
'Уйти'
|
||
], (index) => {
|
||
if (index === 0) {
|
||
this.game.inventory.addItem('bread', 2);
|
||
this.game.inventory.addItem('tea', 1);
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + 10);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Вам дали хлеб и чай. +10 Тепло', 'good');
|
||
} else if (index === 1) {
|
||
this.game.inventory.addItem('clothing', 1);
|
||
this.stats.warmth = Math.min(100, this.stats.warmth + 10);
|
||
this.game.sound.playPickup();
|
||
this.game.notify('Вам дали тёплую одежду. +10 Тепло', 'good');
|
||
} else if (index === 2) {
|
||
this.stats.mood = Math.min(100, this.stats.mood + 20);
|
||
this.stats.health = Math.min(100, this.stats.health + 5);
|
||
this.game.notify('Вы чувствуете покой. +20 Настроение, +5 Здоровье', 'good');
|
||
}
|
||
this.game.ui.hideDialog();
|
||
this.game.questSystem.onEvent('visit_church');
|
||
this.game.reputation.change(2);
|
||
});
|
||
}
|
||
|
||
trackLocations() {
|
||
const cfg = this.game.world.mapConfig?.structures || {};
|
||
const locs = {
|
||
shop: { x: cfg.shop?.x ?? -25, z: cfg.shop?.z ?? -12, r: 8 },
|
||
church: { x: cfg.church?.x ?? 30, z: cfg.church?.z ?? 60, r: 10 },
|
||
park: { x: cfg.park?.x ?? -30, z: cfg.park?.z ?? 25, r: cfg.park?.radius ?? 18 },
|
||
shelter: { x: cfg.shelter?.x ?? -35, z: cfg.shelter?.z ?? 35, r: 6 },
|
||
construction: { x: cfg.construction?.x ?? 70, z: cfg.construction?.z ?? 60, r: cfg.construction?.radius ?? 8 },
|
||
hospital: { x: cfg.hospital?.x ?? -45, z: cfg.hospital?.z ?? -55, r: 8 },
|
||
market: { x: cfg.market?.x ?? 35, z: cfg.market?.z ?? -55, r: 8 },
|
||
busstop: { x: cfg.busStop?.x ?? -20, z: cfg.busStop?.z ?? 7, r: 5 },
|
||
};
|
||
|
||
for (const [name, loc] of Object.entries(locs)) {
|
||
const dx = this.position.x - loc.x;
|
||
const dz = this.position.z - loc.z;
|
||
if (Math.sqrt(dx * dx + dz * dz) < loc.r) {
|
||
this.game.visitedLocations.add(name);
|
||
}
|
||
}
|
||
|
||
if (this.game.visitedLocations.size >= 8) {
|
||
this.game.achievements.check('explorer');
|
||
}
|
||
}
|
||
|
||
checkDanger() {
|
||
return this.game.dangers.hasNearbyDanger();
|
||
}
|
||
}
|