Initial commit: 3D Hommie RPG game

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-02-25 01:04:09 +03:00
commit fb5f09212b
34 changed files with 14550 additions and 0 deletions

230
js/game/Events.js Normal file
View File

@@ -0,0 +1,230 @@
export class EventSystem {
constructor(game) {
this.game = game;
this.timer = 30 + Math.random() * 60;
this.eventCooldown = 0;
this.activeEvent = null;
}
update(dt) {
if (this.eventCooldown > 0) {
this.eventCooldown -= dt;
return;
}
this.timer -= dt;
if (this.timer <= 0) {
this.triggerRandom();
this.timer = 40 + Math.random() * 80;
}
}
triggerRandom() {
const hour = this.game.gameTime / 60;
const isNight = hour < 6 || hour > 21;
const player = this.game.player;
const events = [
{ weight: 10, fn: () => this.eventFoundWallet() },
{ weight: 8, fn: () => this.eventStrayDogFood() },
{ weight: 7, fn: () => this.eventOldFriend() },
{ weight: 6, fn: () => this.eventRainOfCoins() },
{ weight: 8, fn: () => this.eventKindStranger() },
{ weight: 5, fn: () => this.eventFoodTruck() },
{ weight: isNight ? 10 : 3, fn: () => this.eventColdWind() },
{ weight: isNight ? 8 : 2, fn: () => this.eventScaryNoise() },
{ weight: player.stats.mood < 30 ? 10 : 3, fn: () => this.eventMemory() },
{ weight: 4, fn: () => this.eventStreetMusician() },
{ weight: player.stats.health < 40 ? 8 : 2, fn: () => this.eventAmbulance() },
{ weight: 5, fn: () => this.eventNewspaper() },
{ weight: 6, fn: () => this.eventPigeons() },
{ weight: isNight ? 2 : 6, fn: () => this.eventSunshine() },
];
const totalWeight = events.reduce((s, e) => s + e.weight, 0);
let roll = Math.random() * totalWeight;
for (const event of events) {
roll -= event.weight;
if (roll <= 0) {
event.fn();
this.eventCooldown = 20;
return;
}
}
}
eventFoundWallet() {
const amount = 20 + Math.floor(Math.random() * 80);
this.game.ui.showDialog('Находка', `Вы нашли на земле кошелёк! Внутри ${amount}₽. Что делать?`, [
`Забрать деньги (+${amount}₽)`,
'Оставить на месте (может кто-то вернётся)',
], (i) => {
if (i === 0) {
this.game.player.stats.money += amount;
this.game.sound.playCoin();
this.game.notify(`+${amount}`, 'good');
this.game.player.stats.mood = Math.max(0, this.game.player.stats.mood - 2);
this.game.reputation.change(-5);
} else {
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 8);
this.game.notify('+8 Настроение. Вы поступили честно.', 'good');
this.game.reputation.change(10);
}
this.game.ui.hideDialog();
});
}
eventStrayDogFood() {
if (this.game.dog && this.game.dog.adopted) return;
const hasBread = this.game.inventory.getCount('bread') > 0;
const choices = hasBread
? ['Покормить хлебом', 'Прогнать']
: ['Погладить', 'Прогнать'];
this.game.ui.showDialog('Событие', 'Бездомный пёс подошёл к вам и смотрит голодными глазами...', choices, (i) => {
if (i === 0) {
if (hasBread) {
this.game.inventory.removeItem('bread', 1);
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 12);
this.game.notify('Пёс виляет хвостом! +12 Настроение', 'good');
// Приручить если ещё не приручен
if (this.game.dog && !this.game.dog.adopted) {
this.game.dog.adopt();
this.game.notify('Пёс решил остаться с вами!', 'good');
this.game.questSystem.onEvent('adopt_dog');
}
} else {
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 5);
this.game.notify('Пёс благодарно лизнул руку.', 'good');
}
} else {
this.game.notify('Пёс убежал с поджатым хвостом...');
this.game.player.stats.mood = Math.max(0, this.game.player.stats.mood - 3);
}
this.game.ui.hideDialog();
});
}
eventOldFriend() {
this.game.ui.showDialog('Событие', 'Вы встретили старого знакомого. Он узнал вас и выглядит смущённым...', [
'"Привет, давно не виделись..."',
'Отвернуться',
], (i) => {
if (i === 0) {
const roll = Math.random();
if (roll < 0.5) {
const amount = 50 + Math.floor(Math.random() * 100);
this.game.player.stats.money += amount;
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 10);
this.game.sound.playCoin();
this.game.notify(`Он дал вам ${amount}₽ и пожелал удачи.`, 'good');
} else {
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 5);
this.game.notify('Он обнял вас и пожелал сил. +5 Настроение', 'good');
}
} else {
this.game.player.stats.mood = Math.max(0, this.game.player.stats.mood - 5);
this.game.notify('Вы отвернулись. Стало грустно.', 'bad');
}
this.game.ui.hideDialog();
});
}
eventRainOfCoins() {
const amount = 5 + Math.floor(Math.random() * 15);
this.game.player.stats.money += amount;
this.game.sound.playCoin();
this.game.notify(`Кто-то обронил мелочь! +${amount}`, 'good');
}
eventKindStranger() {
this.game.ui.showDialog('Событие', 'К вам подошёл человек в дорогом пальто. "Я из благотворительной организации. Хотите горячий обед?"', [
'Да, спасибо!',
'Нет, обойдусь',
], (i) => {
if (i === 0) {
this.game.player.stats.hunger = Math.min(100, this.game.player.stats.hunger + 50);
this.game.player.stats.warmth = Math.min(100, this.game.player.stats.warmth + 15);
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 15);
this.game.sound.playEat();
this.game.notify('Вы сытно поели! +50 Сытость, +15 Тепло, +15 Настроение', 'good');
}
this.game.ui.hideDialog();
});
}
eventFoodTruck() {
this.game.notify('Фудтрак раздаёт бесплатную еду рядом!');
this.game.inventory.addItem('can', 1);
this.game.inventory.addItem('tea', 1);
this.game.sound.playPickup();
this.game.notify('Получено: Консервы, Чай', 'good');
}
eventColdWind() {
this.game.player.stats.warmth = Math.max(0, this.game.player.stats.warmth - 15);
this.game.player.stats.mood = Math.max(0, this.game.player.stats.mood - 5);
this.game.notify('Порыв ледяного ветра! -15 Тепло', 'bad');
}
eventScaryNoise() {
this.game.player.stats.mood = Math.max(0, this.game.player.stats.mood - 8);
this.game.notify('Странный шум в темноте... -8 Настроение', 'bad');
this.game.sound.playHurt();
}
eventMemory() {
const memories = [
'Вы вспомнили детство... тёплый дом, мамин суп...',
'В голове всплыло лицо старого друга... Где он сейчас?',
'Вы нашли в кармане старую фотографию. Сердце защемило.',
'Знакомая мелодия из окна... Вы когда-то танцевали под неё.',
];
const text = memories[Math.floor(Math.random() * memories.length)];
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 3);
this.game.notify(text);
}
eventStreetMusician() {
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 10);
this.game.notify('Уличный музыкант играет красивую мелодию. +10 Настроение', 'good');
}
eventAmbulance() {
this.game.ui.showDialog('Событие', 'Медработник-волонтёр заметил вас. "Давайте я вас осмотрю?"', [
'Да, пожалуйста',
'Не нужно',
], (i) => {
if (i === 0) {
this.game.player.stats.health = Math.min(100, this.game.player.stats.health + 30);
this.game.inventory.addItem('bandage', 1);
this.game.notify('+30 Здоровье. Получен: Бинт', 'good');
}
this.game.ui.hideDialog();
});
}
eventNewspaper() {
this.game.inventory.addItem('newspaper', 1);
this.game.sound.playPickup();
this.game.notify('Ветер принёс газету. Можно почитать.', 'good');
}
eventPigeons() {
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 3);
this.game.notify('Стая голубей приземлилась рядом. Маленькая радость.');
}
eventSunshine() {
this.game.player.stats.warmth = Math.min(100, this.game.player.stats.warmth + 8);
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 5);
this.game.notify('Тёплый луч солнца согрел лицо. +8 Тепло', 'good');
}
reset() {
this.timer = 30 + Math.random() * 60;
this.eventCooldown = 0;
this.activeEvent = null;
}
}