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

188
js/game/JobSystem.js Normal file
View File

@@ -0,0 +1,188 @@
export class JobSystem {
constructor(game) {
this.game = game;
this.jobs = [];
this.activeJob = null;
this.jobTimer = 0;
this.completedToday = 0;
this.lastDay = 0;
}
init() {
this.generateJobs();
}
generateJobs() {
const allJobs = [
{ id: 'wash_car', name: 'Помыть машину', pay: 40, duration: 15, desc: 'Помыть машину на парковке', location: 'parking', skill: 'trading' },
{ id: 'unload', name: 'Разгрузить товар', pay: 80, duration: 25, desc: 'Разгрузить товар у магазина', location: 'shop', skill: 'survival' },
{ id: 'flyers', name: 'Раздать листовки', pay: 30, duration: 12, desc: 'Раздавать листовки на дороге', location: 'road', skill: 'begging' },
{ id: 'sweep', name: 'Подмести двор', pay: 25, duration: 10, desc: 'Навести порядок у церкви', location: 'church', skill: 'survival' },
{ id: 'help_granny', name: 'Помочь с сумками', pay: 35, duration: 15, desc: 'Донести сумки в парк', location: 'park', skill: 'survival' },
{ id: 'collect_trash', name: 'Собрать мусор', pay: 20, duration: 8, desc: 'Собрать мусор в парке', location: 'park', skill: 'scavenging' },
{ id: 'guard_stuff', name: 'Посторожить багаж', pay: 45, duration: 20, desc: 'Посторожить вещи на остановке', location: 'busstop', skill: 'survival' },
];
// Выбираем 3 случайных
const shuffled = allJobs.sort(() => Math.random() - 0.5);
this.jobs = shuffled.slice(0, 3).map(j => ({ ...j, available: true }));
}
getLocationPosition(loc) {
const positions = {
parking: { x: 40, z: -55 },
shop: { x: 20, z: -20 },
road: { x: 0, z: 0 },
church: { x: 70, z: 50 },
park: { x: -30, z: 40 },
busstop: { x: -38, z: -10 },
};
return positions[loc] || { x: 0, z: 0 };
}
getLocationName(loc) {
const names = {
parking: 'парковке',
shop: 'магазину',
road: 'дороге',
church: 'церкви',
park: 'парку',
busstop: 'остановке',
};
return names[loc] || 'месту';
}
showJobBoard() {
if (this.activeJob) {
this.game.notify('Вы уже выполняете работу!');
return;
}
const availableJobs = this.jobs.filter(j => j.available);
if (availableJobs.length === 0) {
this.game.ui.showDialog('Доска объявлений', 'Сейчас нет доступных подработок. Загляните завтра.', [
'Ладно'
], () => this.game.ui.hideDialog());
return;
}
const payMod = this.game.reputation.getJobPayModifier();
const choices = availableJobs.map(j => {
const pay = Math.floor(j.pay * payMod);
return `${j.name}${pay}₽ (${j.desc})`;
});
choices.push('Уйти');
this.game.sound.playDialogOpen();
this.game.ui.showDialog('Доска объявлений', 'Доступные подработки:', choices, (index) => {
if (index < availableJobs.length) {
this.startJob(availableJobs[index]);
}
this.game.ui.hideDialog();
});
}
startJob(job) {
this.activeJob = { ...job };
this.jobTimer = 0;
this.activeJob.isWorking = false;
this.activeJob.targetPos = this.getLocationPosition(job.location);
const locName = this.getLocationName(job.location);
this.game.notify(`Работа принята: ${job.name}. Идите к ${locName}!`);
}
update(dt) {
// Обновляем список работ при новом дне
if (this.game.gameDay !== this.lastDay) {
this.lastDay = this.game.gameDay;
this.completedToday = 0;
this.generateJobs();
}
if (!this.activeJob) return;
const player = this.game.player;
const pos = this.activeJob.targetPos;
const dx = player.position.x - pos.x;
const dz = player.position.z - pos.z;
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist < 10) {
if (!this.activeJob.isWorking) {
this.activeJob.isWorking = true;
this.game.notify('Вы на месте. Работаете... (оставайтесь рядом)');
}
this.jobTimer += dt;
const progress = Math.min(1, this.jobTimer / this.activeJob.duration);
this.game.ui.updateJobProgress(progress, this.activeJob.name);
if (this.jobTimer >= this.activeJob.duration) {
this.completeJob();
}
} else if (this.activeJob.isWorking) {
this.activeJob.isWorking = false;
this.game.notify('Вы ушли с рабочего места! Вернитесь!', 'bad');
}
}
completeJob() {
const payMod = this.game.reputation.getJobPayModifier();
const pay = Math.floor(this.activeJob.pay * payMod);
this.game.player.stats.money += pay;
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 8);
this.game.sound.playCoin();
this.game.reputation.change(3);
this.game.skills.addXP(this.activeJob.skill || 'survival', 3);
this.game.notify(`Работа выполнена! +${pay}₽, +8 Настроение`, 'good');
this.game.questSystem.onEvent('complete_job');
this.game.totalJobsCompleted++;
// Ачивки
this.game.achievements.check('first_job_done');
if (this.game.totalJobsCompleted >= 10) {
this.game.achievements.check('jobs_10');
}
const idx = this.jobs.findIndex(j => j.id === this.activeJob.id);
if (idx >= 0) this.jobs[idx].available = false;
this.activeJob = null;
this.jobTimer = 0;
this.completedToday++;
this.game.ui.hideJobProgress();
}
cancelJob() {
if (this.activeJob) {
this.game.notify('Работа отменена.', 'bad');
this.game.reputation.change(-2);
this.activeJob = null;
this.jobTimer = 0;
this.game.ui.hideJobProgress();
}
}
getSaveData() {
return {
completedToday: this.completedToday,
lastDay: this.lastDay
};
}
loadSaveData(data) {
if (data) {
this.completedToday = data.completedToday || 0;
this.lastDay = data.lastDay || 0;
}
}
reset() {
this.activeJob = null;
this.jobTimer = 0;
this.completedToday = 0;
this.lastDay = 0;
this.generateJobs();
}
}