Initial commit: 3D Hommie RPG game
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
163
js/game/Achievements.js
Normal file
163
js/game/Achievements.js
Normal file
@@ -0,0 +1,163 @@
|
||||
export class Achievements {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.unlocked = new Set();
|
||||
|
||||
this.list = [
|
||||
// Выживание
|
||||
{ id: 'first_day', title: 'Новая жизнь', desc: 'Прожить 1 день', icon: '🌅', category: 'survival' },
|
||||
{ id: 'week_survivor', title: 'Недельный марафон', desc: 'Прожить 7 дней', icon: '📅', category: 'survival' },
|
||||
{ id: 'month_survivor', title: 'Бывалый', desc: 'Прожить 28 дней', icon: '🏆', category: 'survival' },
|
||||
{ id: 'winter_survivor', title: 'Морозоустойчивый', desc: 'Пережить зиму', icon: '❄️', category: 'survival' },
|
||||
{ id: 'full_year', title: 'Годовщина', desc: 'Прожить все 4 сезона', icon: '🎊', category: 'survival' },
|
||||
{ id: 'near_death', title: 'На грани', desc: 'Выжить при здоровье < 5', icon: '💀', category: 'survival' },
|
||||
{ id: 'well_fed', title: 'Сытый', desc: 'Держать сытость на 100 в течение 5 минут', icon: '🍽️', category: 'survival' },
|
||||
|
||||
// Социальные
|
||||
{ id: 'first_talk', title: 'Общительный', desc: 'Поговорить с 3 NPC', icon: '💬', category: 'social' },
|
||||
{ id: 'all_npcs', title: 'Душа компании', desc: 'Поговорить со всеми NPC', icon: '🤝', category: 'social' },
|
||||
{ id: 'best_friend', title: 'Лучший друг', desc: 'Приручить пса', icon: '🐕', category: 'social' },
|
||||
{ id: 'respected', title: 'Уважаемый', desc: 'Достичь репутации 50+', icon: '⭐', category: 'social' },
|
||||
{ id: 'beloved', title: 'Свой человек', desc: 'Достичь репутации 80+', icon: '💛', category: 'social' },
|
||||
|
||||
// Экономика
|
||||
{ id: 'first_money', title: 'Первый рубль', desc: 'Заработать первые деньги', icon: '💰', category: 'economy' },
|
||||
{ id: 'rich_100', title: 'Копилка', desc: 'Накопить 100₽', icon: '🪙', category: 'economy' },
|
||||
{ id: 'rich_500', title: 'Состояние', desc: 'Накопить 500₽', icon: '💎', category: 'economy' },
|
||||
{ id: 'rich_1000', title: 'Богач', desc: 'Накопить 1000₽', icon: '👑', category: 'economy' },
|
||||
{ id: 'first_job_done', title: 'Трудяга', desc: 'Выполнить первую подработку', icon: '🔧', category: 'economy' },
|
||||
{ id: 'jobs_10', title: 'Работяга', desc: 'Выполнить 10 подработок', icon: '🏗️', category: 'economy' },
|
||||
{ id: 'bottle_king', title: 'Король бутылок', desc: 'Сдать 20 бутылок', icon: '🍾', category: 'economy' },
|
||||
|
||||
// Боевые
|
||||
{ id: 'first_fight', title: 'Боец', desc: 'Отбиться от врага', icon: '👊', category: 'combat' },
|
||||
{ id: 'fighter_5', title: 'Бывалый боец', desc: 'Победить 5 врагов', icon: '🥊', category: 'combat' },
|
||||
{ id: 'survivor_combat', title: 'Несгибаемый', desc: 'Отбиться от 3 врагов подряд', icon: '🛡️', category: 'combat' },
|
||||
|
||||
// Исследование
|
||||
{ id: 'explorer', title: 'Исследователь', desc: 'Посетить все локации', icon: '🗺️', category: 'explore' },
|
||||
{ id: 'crafter', title: 'Мастер', desc: 'Создать 5 предметов', icon: '🔨', category: 'explore' },
|
||||
{ id: 'skill_max', title: 'Эксперт', desc: 'Достичь 5 уровня любого навыка', icon: '📚', category: 'explore' },
|
||||
{ id: 'fully_equipped', title: 'Экипированный', desc: 'Заполнить все слоты экипировки', icon: '🛡️', category: 'explore' },
|
||||
{ id: 'shelter_built', title: 'Свой угол', desc: 'Построить укрытие', icon: '🏠', category: 'explore' },
|
||||
];
|
||||
}
|
||||
|
||||
// Вызывается из разных мест кода
|
||||
check(id) {
|
||||
if (this.unlocked.has(id)) return;
|
||||
|
||||
const achievement = this.list.find(a => a.id === id);
|
||||
if (!achievement) return;
|
||||
|
||||
this.unlocked.add(id);
|
||||
this.showPopup(achievement);
|
||||
this.game.sound.playQuestComplete();
|
||||
this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 5);
|
||||
}
|
||||
|
||||
// Проверки, вызываемые каждый тик
|
||||
updateChecks() {
|
||||
const player = this.game.player;
|
||||
const day = this.game.gameDay;
|
||||
|
||||
// Выживание
|
||||
if (day >= 2) this.check('first_day');
|
||||
if (day >= 8) this.check('week_survivor');
|
||||
if (day >= 29) this.check('month_survivor');
|
||||
|
||||
// Здоровье
|
||||
if (player.stats.health > 0 && player.stats.health < 5) {
|
||||
this.check('near_death');
|
||||
}
|
||||
|
||||
// Деньги
|
||||
if (player.stats.money >= 1) this.check('first_money');
|
||||
if (player.stats.money >= 100) this.check('rich_100');
|
||||
if (player.stats.money >= 500) this.check('rich_500');
|
||||
if (player.stats.money >= 1000) this.check('rich_1000');
|
||||
|
||||
// Репутация
|
||||
if (this.game.reputation.value >= 50) this.check('respected');
|
||||
if (this.game.reputation.value >= 80) this.check('beloved');
|
||||
|
||||
// Пёс
|
||||
if (this.game.dog.adopted) this.check('best_friend');
|
||||
|
||||
// Навыки
|
||||
const skills = this.game.skills.skills;
|
||||
for (const skill of Object.values(skills)) {
|
||||
if (skill.level >= 5) {
|
||||
this.check('skill_max');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Экипировка
|
||||
if (this.game.equipment.getFilledSlots() === 4) {
|
||||
this.check('fully_equipped');
|
||||
}
|
||||
}
|
||||
|
||||
showPopup(achievement) {
|
||||
let popup = document.getElementById('achievement-popup');
|
||||
if (!popup) {
|
||||
popup = document.createElement('div');
|
||||
popup.id = 'achievement-popup';
|
||||
popup.style.cssText = `
|
||||
position: fixed;
|
||||
top: -80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, rgba(20,20,40,0.95), rgba(40,30,60,0.95));
|
||||
border: 2px solid #ffd740;
|
||||
border-radius: 8px;
|
||||
padding: 12px 24px;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
transition: top 0.5s ease;
|
||||
backdrop-filter: blur(8px);
|
||||
box-shadow: 0 4px 20px rgba(255,215,64,0.3);
|
||||
pointer-events: none;
|
||||
`;
|
||||
document.body.appendChild(popup);
|
||||
}
|
||||
|
||||
popup.innerHTML = `
|
||||
<span style="font-size:2rem;">${achievement.icon}</span>
|
||||
<div>
|
||||
<div style="font-size:0.7rem;color:#ffd740;font-weight:700;text-transform:uppercase;letter-spacing:0.1em;">Достижение разблокировано!</div>
|
||||
<div style="font-size:1rem;font-weight:700;color:#fff;margin-top:2px;">${achievement.title}</div>
|
||||
<div style="font-size:0.75rem;color:#aaa;">${achievement.desc}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Анимация
|
||||
setTimeout(() => popup.style.top = '20px', 50);
|
||||
setTimeout(() => popup.style.top = '-80px', 4000);
|
||||
}
|
||||
|
||||
getByCategory(category) {
|
||||
return this.list.filter(a => a.category === category);
|
||||
}
|
||||
|
||||
getProgress() {
|
||||
return { unlocked: this.unlocked.size, total: this.list.length };
|
||||
}
|
||||
|
||||
getSaveData() {
|
||||
return { unlocked: [...this.unlocked] };
|
||||
}
|
||||
|
||||
loadSaveData(data) {
|
||||
if (data && data.unlocked) {
|
||||
this.unlocked = new Set(data.unlocked);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.unlocked = new Set();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user