Загрузить файлы в «Hommie»

This commit is contained in:
2026-02-23 21:16:00 +03:00
parent 1954252cd0
commit 3870fe54d4
5 changed files with 1578 additions and 0 deletions

303
Hommie/audio.js Normal file
View File

@@ -0,0 +1,303 @@
/**
* Audio System - Звуковая система (Web Audio API)
*/
class AudioSystem {
constructor() {
this.context = null;
this.enabled = true;
this.volume = 0.5;
this.musicEnabled = true;
this.musicOscillator = null;
this.musicGain = null;
}
// Инициализация аудио контекста
init() {
try {
this.context = new (window.AudioContext || window.webkitAudioContext)();
return true;
} catch (e) {
console.warn('Audio not supported:', e);
return false;
}
}
// Возобновить контекст (нужно после взаимодействия пользователя)
resume() {
if (this.context && this.context.state === 'suspended') {
this.context.resume();
}
}
// Звук шагов
playFootstep() {
if (!this.enabled || !this.context) return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'square';
osc.frequency.setValueAtTime(80, this.context.currentTime);
osc.frequency.exponentialRampToValueAtTime(40, this.context.currentTime + 0.1);
gain.gain.setValueAtTime(this.volume * 0.1, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.1);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.1);
}
// Звук удара
playHit() {
if (!this.enabled || !this.context) return;
// Шум удара
const bufferSize = this.context.sampleRate * 0.1;
const buffer = this.context.createBuffer(1, bufferSize, this.context.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
}
const noise = this.context.createBufferSource();
noise.buffer = buffer;
const filter = this.context.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1000;
const gain = this.context.createGain();
gain.gain.setValueAtTime(this.volume * 0.3, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.15);
noise.connect(filter);
filter.connect(gain);
gain.connect(this.context.destination);
noise.start();
}
// Звук убийства
playKill() {
if (!this.enabled || !this.context) return;
// Низкий удар
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, this.context.currentTime);
osc.frequency.exponentialRampToValueAtTime(30, this.context.currentTime + 0.3);
gain.gain.setValueAtTime(this.volume * 0.4, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.3);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.3);
}
// Звукденег
playCoin() {
if (!this.enabled || !this.context) return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(800, this.context.currentTime);
osc.frequency.setValueAtTime(1200, this.context.currentTime + 0.05);
osc.frequency.setValueAtTime(1600, this.context.currentTime + 0.1);
gain.gain.setValueAtTime(this.volume * 0.2, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.15);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.15);
}
// Звук предмета
playItem() {
if (!this.enabled || !this.context) return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(600, this.context.currentTime);
osc.frequency.setValueAtTime(900, this.context.currentTime + 0.08);
gain.gain.setValueAtTime(this.volume * 0.15, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.2);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.2);
}
// Звук еды
playEat() {
if (!this.enabled || !this.context) return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'triangle';
osc.frequency.setValueAtTime(400, this.context.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, this.context.currentTime + 0.15);
gain.gain.setValueAtTime(this.volume * 0.2, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.15);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.15);
}
// Звук HP
playHeal() {
if (!this.enabled || !this.context) return;
const osc1 = this.context.createOscillator();
const osc2 = this.context.createOscillator();
const gain = this.context.createGain();
osc1.connect(gain);
osc2.connect(gain);
gain.connect(this.context.destination);
osc1.type = 'sine';
osc1.frequency.setValueAtTime(500, this.context.currentTime);
osc1.frequency.setValueAtTime(700, this.context.currentTime + 0.1);
osc2.type = 'sine';
osc2.frequency.setValueAtTime(750, this.context.currentTime + 0.05);
osc2.frequency.setValueAtTime(1000, this.context.currentTime + 0.15);
gain.gain.setValueAtTime(this.volume * 0.2, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.25);
osc1.start(this.context.currentTime);
osc2.start(this.context.currentTime + 0.05);
osc1.stop(this.context.currentTime + 0.2);
osc2.stop(this.context.currentTime + 0.25);
}
// Звук получения урона
playDamage() {
if (!this.enabled || !this.context) return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, this.context.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, this.context.currentTime + 0.2);
gain.gain.setValueAtTime(this.volume * 0.3, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.2);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.2);
}
// Звук UI (кнопка)
playUIClick() {
if (!this.enabled || !this.context) return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.connect(gain);
gain.connect(this.context.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(1000, this.context.currentTime);
gain.gain.setValueAtTime(this.volume * 0.1, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.05);
osc.start(this.context.currentTime);
osc.stop(this.context.currentTime + 0.05);
}
// Фоновая музыка (простая мелодия)
startMusic() {
if (!this.musicEnabled || !this.context) return;
// Остановить предыдущую музыку
this.stopMusic();
// Создать осциллятор для фоновой музыки
this.musicOscillator = this.context.createOscillator();
this.musicGain = this.context.createGain();
this.musicOscillator.connect(this.musicGain);
this.musicGain.connect(this.context.destination);
this.musicOscillator.type = 'sine';
this.musicOscillator.frequency.value = 220;
this.musicGain.gain.value = this.volume * 0.05;
// Менять частоту каждые 2 секунды
const notes = [220, 262, 294, 330, 349, 392, 440, 392, 349, 330, 294, 262];
let noteIndex = 0;
this.musicInterval = setInterval(() => {
if (this.musicEnabled && this.musicOscillator) {
this.musicOscillator.frequency.value = notes[noteIndex];
noteIndex = (noteIndex + 1) % notes.length;
}
}, 2000);
this.musicOscillator.start();
}
// Остановить музыку
stopMusic() {
if (this.musicInterval) {
clearInterval(this.musicInterval);
}
if (this.musicOscillator) {
this.musicOscillator.stop();
this.musicOscillator = null;
}
}
// Переключить звук
toggle() {
this.enabled = !this.enabled;
return this.enabled;
}
// Переключить музыку
toggleMusic() {
this.musicEnabled = !this.musicEnabled;
if (this.musicEnabled) {
this.startMusic();
} else {
this.stopMusic();
}
return this.musicEnabled;
}
}
// Глобальный экземпляр
window.Audio = new AudioSystem();

588
Hommie/data.js Normal file
View File

@@ -0,0 +1,588 @@
/**
* Game Data - Все данные игры (FENYA EDITION)
*/
const GameData = {
// Размеры карты
TILE_SIZE: 40,
MAP_WIDTH: 20,
MAP_HEIGHT: 15,
// Типы предметов (с иконками)
ITEM_ICONS: {
'🍞 Хлеб': '🍞',
'🍎 Яблоко': '🍎',
'🥫 Консерва': '🥫',
'🍜 Остатки': '🍜',
'🥡 Картонка': '🥡',
'🥤 Напиток': '🥤',
'💊 Таблетки': '💊',
'💊 Лекарства': '💊',
'📦 Коробка': '📦',
'🧥 Куртка': '🧥',
'🧴 Мыло': '🧴',
'🎫 Билет': '🎫',
'🛒 Продукты': '🛒',
'🔪 Заточка': '🔪',
'🍺 Стекломой': '🍺',
'💨 Бутырка': '💨',
'⚱️ Марганцовка': '⚱️',
'🍾 Водка': '🍾',
'🍶 Самогон': '🍶',
'🍷 Плодовоягодное': '🍷'
},
// Еда
FOOD_ITEMS: ['🍞 Хлеб', '🍎 Яблоко', '🥫 Консерва', '🍜 Остатки', '🥡 Картонка', '🥤 Напиток'],
// Полезные предметы
USEFUL_ITEMS: ['💊 Таблетки', '📦 Коробка', '🧥 Куртка', '🧴 Мыло'],
// Оружие
WEAPONS: ['🔪 Заточка', '🍺 Стекломой'],
// Наркотики и алкоголь
DRUGS: ['💨 Бутырка', '⚱️ Марганцовка'],
ALCOHOL: ['🍾 Водка', '🍶 Самогон', '🍷 Плодовоягодное'],
// Цены и значения
PRICES: {
food: 50,
drink: 30,
medicine: 100,
ticket: 50,
weapons: 200,
drugs: 150
},
// Эффекты
EFFECTS: {
foodHealth: 15,
foodEnergy: 20,
pillHealth: 30,
medicineHealth: 50,
drinkEnergy: 15,
restEnergy: 30,
restHealth: 10,
bedEnergy: 50,
bedHealth: 20,
nightHealthLoss: 5,
starvationHealthLoss: 0.1,
drugHigh: 50,
drugEnergy: 100,
drugHealthLoss: 20,
vodkaEnergy: 80,
vodkaHealthLoss: 15,
samogonEnergy: 100,
samogonHealthLoss: 25,
plodovoEnergy: 60,
plodovoHealthLoss: 10,
combatDamage: 15,
combatEnemyDamage: 10
},
// FENYA - Диалоги и названия
FENYA: {
// Приветствия
greetings: [
'Здорово, кент!',
'Чё, как оно?',
'Привет, бродяга!',
'Здорово, бомж!'
],
// Прощания
farewells: [
'Давай, не пропадай!',
'Сiao, кент!',
'Не звезди!',
'Пока, братва!'
],
// Описания
descriptions: {
street: 'Качай двор, кент. Тут можо шариться по мусорняку и на халяву поесть.',
subway: 'Метро - наша тема. Тепло, народ ходит, можно стрельнуть.',
hospital: 'Больничка - тут могут подлатать, но халявы нет.',
shelter: 'Барделка (приют) - перекантоваться можно, но там козлы.',
park: 'Парк - тут качаются и отдыхают нормальные пацанчики.'
},
// Действия
actions: {
search: 'Роюсь в мусоре...',
rest: 'Отдыхаю, братва...',
eat: 'Жру, что нашёл...',
fight: 'Бью морду!'
}
},
// NPC диалоги (по фене)
NPC_DIALOGS: {
// Улица - светлые бомжи
street: {
person: [
'Здорово, кент! Как покатушки?',
'Чё, в поиске? Я тоже тут торчу.',
'Видел нормальную малину?',
'Давай, не сдавайся, брат!',
'Я тут третий день качаюсь...',
'Бизнес идёт? Да никак, кент...'
],
person2: [
'Привет, новенький? Я тут главный!',
'Мусарка за углом - твоя, не лезь в мой район!',
'Чё, на халяву хочешь? Давай работай!',
'Я бомж со стажем, кент!'
]
},
// Барыги (торговцы)
dealer: [
'Чё, кент, чё надо?',
'Есть всё, но за бабки!',
'Могу подогнать, но дорого!',
'Хочешь норму - плати!',
'Стукач? Не, я честный барыга!'
],
// Метро
subway: {
musician: [
'Эй, хороший трек, правда?',
'Дам бабло, играй ещё!',
'Слышал новый трек?',
'Вот это я понимаю - искусство, кент!'
],
beggar: [
'Подай на хлебушек...',
'Смилуйся, брат...',
'Три дня не жрал...',
'Люди злые стали, кент...'
]
},
// Больница
hospital: {
doctor: [
'Тебе нужна помощь? Приём платный - 200 рублей!',
'Без бабла не лечим!',
'Можешь полежать в коридоре, если очень надо!',
'Вот таблетки от головы, бесплатно'
],
nurse: [
'Доктор сейчас занят...',
'Очередь на приём - три часа!',
'Воды хочешь? Бесплатно!',
'Не шуми, тут больные!'
],
patient: [
'Лежу тут уже третью неделю...',
'Врачи нормальные, но очередь - жесть!',
'Хочу домой, но бабла нет на лечение...',
'Болезнь замучила, кент...'
]
},
// Приют
shelter: {
volunteer: [
'Привет! Хочешь поесть? Сейчас раздача через час!',
'Можешь убраться - заплатим 50 рублей!',
'Вот шмотки, держи, не мёрзни!',
'Не сдавайся, всё будет норм, кент!',
'Кровать свободна, отдыхай!'
]
},
// Парк
park: {
jogger: [
'Бегаю каждый день для здоровья!',
'Не хочешь присоединиться, кент?',
'Свежий воздух - это жизнь!'
],
dogWalker: [
'Собаки - наши друзья!',
'Не трогай их, могут укусить!',
'Погладь, не бойся!'
],
benchPerson: [
'Красивый парк, правда?',
'Хорошая погодка для качалки!',
'Тут всегда тихо, кент...'
]
}
},
// Тёмные бомжи (враги ночью)
DARK_HOMELESS: [
{ name: 'Отморозок', damage: 10, health: 30, phrase: 'Гони бабло, козёл!' },
{ name: 'Бомжара', damage: 15, health: 40, phrase: 'Щас тебе наваляю!' },
{ name: 'Чугун', damage: 20, health: 50, phrase: 'Ты мой, сука!' },
{ name: 'Кабан', damage: 25, health: 60, phrase: 'Бей его!' }
],
// Лут с врагов
ENEMY_LOOT: [
'🍞 Хлеб',
'🥤 Напиток',
'💊 Таблетки',
'💰 Копейка',
'📦 Коробка'
],
// Цена оружия и алкоголя у барыг
DEALER_PRICES: {
'🔪 Заточка': 200,
'🍺 Стекломой': 150,
'💨 Бутырка': 100,
'⚱️ Марганцовка': 200,
'🍾 Водка': 80,
'🍶 Самогон': 50,
'🍷 Плодовоягодное': 30,
'🍞 Хлеб': 30,
'🥤 Напиток': 20,
'💊 Таблетки': 50
},
// Магазины
SHOPS: {
street: {
name: 'Магазин',
icon: '🏪',
items: [
{ item: '🍞 Хлеб', price: 30 },
{ item: '🥤 Напиток', price: 20 },
{ item: '💊 Таблетки', price: 80 },
{ item: '🧴 Мыло', price: 25 },
{ item: '🧥 Куртка', price: 150 }
]
},
subway: {
name: 'Киоск',
icon: '🏪',
items: [
{ item: '🍞 Хлеб', price: 25 },
{ item: '🥤 Напиток', price: 15 },
{ item: '🎫 Билет', price: 50 }
]
},
hospital: {
name: 'Аптека',
icon: '💊',
items: [
{ item: '💊 Таблетки', price: 100 },
{ item: '💊 Лекарства', price: 200 }
]
},
shelter: {
name: 'Приют',
icon: '🏠',
items: [
{ item: '🍞 Хлеб', price: 10 },
{ item: '🧥 Куртка', price: 50 },
{ item: '🧴 Мыло', price: 10 }
]
},
park: {
name: 'Лавка',
icon: '🏪',
items: [
{ item: '🍎 Яблоко', price: 20 },
{ item: '🥤 Напиток', price: 25 },
{ item: '🍜 Остатки', price: 40 }
]
}
},
// Карта мира
WORLD_MAP: {
name: 'Карта города',
icon: '🗺️',
locations: {
street: { x: 50, y: 50, name: 'Улица', icon: '🏙️', cost: 0 },
subway: { x: 30, y: 70, name: 'Метро', icon: '🚇', cost: 25 },
park: { x: 70, y: 30, name: 'Парк', icon: '🌳', cost: 0 },
hospital: { x: 80, y: 70, name: 'Больница', icon: '🏥', cost: 50 },
shelter: { x: 20, y: 30, name: 'Приют', icon: '🏠', cost: 30 },
beach: { x: 10, y: 80, name: 'Пляж', icon: '🏖️', cost: 20 },
construction: { x: 90, y: 40, name: 'Стройка', icon: '🏗️', cost: 0 },
river: { x: 60, y: 10, name: 'Река', icon: '🌊', cost: 10 }
}
},
// Ломбард
PAWNSHOP: {
name: 'Ломбард',
icon: '🏦',
prices: {
'🧥 Куртка': 80,
'🧴 Мыло': 15,
'🔪 Заточка': 100,
'🍺 Стекломой': 75,
'📦 Коробка': 10
}
},
// Система крафтинга
CRAFTING_RECIPES: [
{
name: '🔪 Заточка',
ingredients: ['📦 Коробка', '🧴 Мыло'],
result: '🔪 Заточка',
description: 'Оружие из подручных материалов',
damage: 20
},
{
name: '🍺 Стекломой',
ingredients: ['🍷 Плодовоягодное', '🧴 Мыло'],
result: '🍺 Стекломой',
description: 'Оружие из бутылки',
damage: 15
},
{
name: '🧥 Тёплая куртка',
ingredients: ['🧥 Куртка', '📦 Коробка'],
result: '🧥 Тёплая куртка',
description: 'Защита от холода',
defense: 10
},
{
name: '🥫 Зелье здоровья',
ingredients: ['💊 Таблетки', '🥤 Напиток'],
result: '🥫 Зелье здоровья',
description: 'Восстанавливает 50 HP',
heal: 50
},
{
name: '🎒 Усиленный рюкзак',
ingredients: ['📦 Коробка', '📦 Коробка'],
result: '🎒 Усиленный рюкзак',
description: '+5 слотов инвентаря',
inventory: 5
}
],
// Система квестов
QUESTS: {
// Квесты улицы
street: [
{
id: 'street_1',
title: 'Найди еду',
description: 'Найди 3 продукта питания в мусорках',
icon: '🍞',
target: 'collect_food',
targetCount: 3,
reward: { money: 50 },
requiredLocation: 'street',
repeat: false
},
{
id: 'street_2',
title: 'Защита района',
description: 'Победи 2 врагов ночью',
icon: '⚔️',
target: 'kill_enemies',
targetCount: 2,
reward: { money: 100, item: '🔪 Заточка' },
requiredLocation: 'street',
repeat: false
},
{
id: 'street_3',
title: 'Подработка',
description: 'Найди 100 рублей за день',
icon: '💰',
target: 'earn_money',
targetCount: 100,
reward: { money: 50 },
requiredLocation: 'street',
repeat: true
}
],
// Квесты метро
subway: [
{
id: 'subway_1',
title: 'Музыкант',
description: 'Послушай музыканта в метро',
icon: '🎵',
target: 'talk_npc',
targetCount: 1,
reward: { money: 20 },
requiredLocation: 'subway',
repeat: false
},
{
id: 'subway_2',
title: 'Безбилетник',
description: 'Проезь без билета 3 раза',
icon: '🎫',
target: 'ride_free',
targetCount: 3,
reward: { money: 30 },
requiredLocation: 'subway',
repeat: true
}
],
// Квесты больницы
hospital: [
{
id: 'hospital_1',
title: 'Медицинская помощь',
description: 'Получи лечение в больнице',
icon: '💉',
target: 'get_treatment',
targetCount: 1,
reward: { health: 50 },
requiredLocation: 'hospital',
repeat: true,
cost: 100
},
{
id: 'hospital_2',
title: 'Таблетки',
description: 'Купи 2 упаковки таблеток',
icon: '💊',
target: 'buy_item',
targetCount: 2,
item: '💊 Таблетки',
reward: { money: 50 },
requiredLocation: 'hospital',
repeat: false
}
],
// Квесты приюта
shelter: [
{
id: 'shelter_1',
title: 'Уборка',
description: 'Уберись в приюте за 50 рублей',
icon: '🧹',
target: 'work',
targetCount: 1,
reward: { money: 50, energy: 30 },
requiredLocation: 'shelter',
repeat: true
},
{
id: 'shelter_2',
title: 'Ночлег',
description: 'Отдохни в приюте',
icon: '🛏️',
target: 'rest',
targetCount: 1,
reward: { energy: 50, health: 20 },
requiredLocation: 'shelter',
repeat: true
}
],
// Квесты парка
park: [
{
id: 'park_1',
title: 'Собеседник',
description: 'Поговори с 3 людьми в парке',
icon: '💬',
target: 'talk_npc',
targetCount: 3,
reward: { money: 30 },
requiredLocation: 'park',
repeat: false
},
{
id: 'park_2',
title: 'Безопасность',
description: 'Победи врага в парке',
icon: '🥊',
target: 'kill_enemies',
targetCount: 1,
reward: { money: 75 },
requiredLocation: 'park',
repeat: true
}
],
// Квесты пляжа
beach: [
{
id: 'beach_1',
title: 'Отдых на пляже',
description: 'Отдохни на пляже',
icon: '🏖️',
target: 'rest',
targetCount: 1,
reward: { health: 20, energy: 30 },
requiredLocation: 'beach',
repeat: true
},
{
id: 'beach_2',
title: 'Мусорщик',
description: 'Найди 5 предметов на пляже',
icon: '🎣',
target: 'collect_food',
targetCount: 5,
reward: { money: 40 },
requiredLocation: 'beach',
repeat: false
}
],
// Квесты стройки
construction: [
{
id: 'construction_1',
title: 'Рабочий день',
description: 'Поработай на стройке',
icon: '🔨',
target: 'work',
targetCount: 1,
reward: { money: 100 },
requiredLocation: 'construction',
repeat: true
},
{
id: 'construction_2',
title: 'Опасная территория',
description: 'Победи охранника',
icon: '⚔️',
target: 'kill_enemies',
targetCount: 1,
reward: { money: 150 },
requiredLocation: 'construction',
repeat: false
}
],
// Квесты реки
river: [
{
id: 'river_1',
title: 'Рыбак',
description: 'Поймай рыбу',
icon: '🐟',
target: 'fish',
targetCount: 1,
reward: { item: '🍜 Остатки' },
requiredLocation: 'river',
repeat: true
},
{
id: 'river_2',
title: 'Купание',
description: 'Искупайся в реке',
icon: '🏊',
target: 'rest',
targetCount: 1,
reward: { health: 15 },
requiredLocation: 'river',
repeat: true
}
]
}
};
// Экспорт для использования
if (typeof module !== 'undefined' && module.exports) {
module.exports = GameData;
}

166
Hommie/effects3d.js Normal file
View File

@@ -0,0 +1,166 @@
/**
* Effects3D Class - Visual Effects for 3D Game
*/
class Effects3D {
constructor(scene) {
this.scene = scene;
this.particles = [];
}
// Создание системы частиц
createParticleSystem(x, y, z, color, count = 20) {
const geometry = new THREE.BufferGeometry();
const positions = [];
const velocities = [];
for (let i = 0; i < count; i++) {
positions.push(x, y, z);
velocities.push(
(Math.random() - 0.5) * 0.3,
Math.random() * 0.2,
(Math.random() - 0.5) * 0.3
);
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({
color: color,
size: 0.15,
transparent: true,
opacity: 1
});
const particles = new THREE.Points(geometry, material);
this.scene.add(particles);
return {
mesh: particles,
velocities: velocities,
life: 1.0,
update: () => {
const positions = particles.geometry.attributes.position.array;
for (let i = 0; i < count; i++) {
positions[i * 3] += velocities[i * 3];
positions[i * 3 + 1] += velocities[i * 3 + 1];
positions[i * 3 + 2] += velocities[i * 3 + 2];
velocities[i * 3 + 1] -= 0.01; // гравитация
}
particles.geometry.attributes.position.needsUpdate = true;
particles.material.opacity = this.life;
this.life -= 0.02;
}
};
}
// Эффект при получении урона
showDamageEffect(x, z) {
const effect = this.createParticleSystem(x, 1, z, 0xff0000, 15);
this.particles.push(effect);
}
// Эффект при получении денег
showMoneyEffect(x, z) {
const effect = this.createParticleSystem(x, 1, z, 0xffd700, 10);
this.particles.push(effect);
}
// Эффект при лечении
showHealEffect(x, z) {
const effect = this.createParticleSystem(x, 1, z, 0x00ff00, 15);
this.particles.push(effect);
}
// Эффект при убийстве врага
showKillEffect(x, z) {
const effect = this.createParticleSystem(x, 1, z, 0xff4444, 25);
this.particles.push(effect);
}
// Обновить все частицы
update() {
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i];
p.update();
if (p.life <= 0) {
this.scene.remove(p.mesh);
p.mesh.geometry.dispose();
p.mesh.material.dispose();
this.particles.splice(i, 1);
}
}
}
// Тряска экрана
shakeScreen(intensity = 0.5) {
const originalPos = Game.camera.position.clone();
const shake = () => {
Game.camera.position.x = originalPos.x + (Math.random() - 0.5) * intensity;
Game.camera.position.y = originalPos.y + (Math.random() - 0.5) * intensity;
};
const interval = setInterval(shake, 30);
setTimeout(() => {
clearInterval(interval);
Game.camera.position.copy(originalPos);
}, 300);
}
// Плавающий текст
createFloatingText(x, y, z, text, color = '#ffffff') {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 64;
context.fillStyle = 'transparent';
context.fillRect(0, 0, canvas.width, canvas.height);
context.font = 'bold 32px Arial';
context.fillStyle = color;
context.textAlign = 'center';
context.fillText(text, 128, 40);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: 1
});
const sprite = new THREE.Sprite(material);
sprite.position.set(x, y + 2, z);
sprite.scale.set(2, 0.5, 1);
this.scene.add(sprite);
// Анимация всплывания
const animate = () => {
sprite.position.y += 0.05;
sprite.material.opacity -= 0.02;
if (sprite.material.opacity <= 0) {
this.scene.remove(sprite);
texture.dispose();
material.dispose();
return;
}
requestAnimationFrame(animate);
};
animate();
}
// Очистка всех эффектов
dispose() {
this.particles.forEach(p => {
this.scene.remove(p.mesh);
p.mesh.geometry.dispose();
p.mesh.material.dispose();
});
this.particles = [];
}
}
// Глобальный экземпляр
window.Effects = null;

135
Hommie/index.html Normal file
View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Симулятор Бомжа 3D</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="game-wrapper">
<div id="game-header">
<h1>🏚️ СИМУЛЯТОР БОМЖА 3D</h1>
<div id="day-time">
<span id="day-display">День 1</span>
<span id="time-display">08:00</span>
</div>
</div>
<div id="game-container">
<div id="game-canvas"></div>
<!-- UI Overlay -->
<div id="ui-overlay">
<!-- Stats Panel -->
<div id="stats-panel" class="panel">
<div class="stat-row">
<div class="stat-icon">❤️</div>
<div class="stat-bar-container">
<div class="stat-bar health-bar" id="health-bar"></div>
</div>
<span class="stat-value" id="health-value">100</span>
</div>
<div class="stat-row">
<div class="stat-icon"></div>
<div class="stat-bar-container">
<div class="stat-bar energy-bar" id="energy-bar"></div>
</div>
<span class="stat-value" id="energy-value">100</span>
</div>
<div class="stat-row">
<div class="stat-icon">💰</div>
<span class="stat-value money-value" id="money-value">0₽</span>
</div>
</div>
<!-- Location Bar -->
<div id="location-bar">
<span id="location-icon">🏙️</span>
<span id="location-name">Улица</span>
</div>
<!-- Inventory Panel -->
<div id="inventory-panel" class="panel">
<div class="panel-header">
<span>🎒 Рюкзак</span>
<span id="inv-count">0/10</span>
</div>
<div id="inventory-grid"></div>
</div>
<!-- Locations Menu -->
<div id="locations-panel" class="panel">
<div class="panel-header">🚇 Локации</div>
<button class="loc-btn" data-location="street" onclick="Game.changeLocation('street')">
<span>🏙️</span> Улица
</button>
<button class="loc-btn" data-location="subway" onclick="Game.changeLocation('subway')">
<span>🚇</span> Метро
</button>
<button class="loc-btn" data-location="park" onclick="Game.changeLocation('park')">
<span>🌳</span> Парк
</button>
<button class="loc-btn" data-location="hospital" onclick="Game.changeLocation('hospital')">
<span>🏥</span> Больница
</button>
<button class="loc-btn" data-location="shelter" onclick="Game.changeLocation('shelter')">
<span>🏠</span> Приют
</button>
</div>
<!-- Action Hint -->
<div id="action-hint">Нажми <span class="key">E</span></div>
</div>
<!-- Message Modal -->
<div id="message-modal" class="modal">
<div id="message-icon"></div>
<div id="message-text"></div>
<button class="modal-btn" onclick="UI.closeMessage()">ОК</button>
</div>
<!-- Title Screen -->
<div id="title-screen">
<div class="title-logo">🏚️</div>
<h1>СИМУЛЯТОР БОМЖА 3D</h1>
<p class="subtitle">Выживание на улице</p>
<button class="start-btn" onclick="initGame()">НАЧАТЬ ИГРУ</button>
<div class="controls-info">
<div><span class="key">W A S D</span> Ходить</div>
<div><span class="key">E</span> Взаимодействовать</div>
<div><span class="key">I</span> Инвентарь</div>
<div><span class="key">C</span> Крафтинг</div>
<div><span class="key">Q</span> Квесты</div>
<div><span class="key">P</span> Магазин</div>
<div><span class="key">L</span> Ломбард</div>
<div><span class="key">M</span> Карта</div>
<div><span class="key">ПРОБЕЛ</span> Атаковать</div>
</div>
</div>
</div>
<div id="game-footer">
<span>🏚️ Homeless Simulator 3D v1.0</span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/EffectComposer.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/RenderPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/UnrealBloomPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/FilmPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/shaders/LuminosityHighPassShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/shaders/CopyShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/ShaderPass.js"></script>
<script src="js/data.js"></script>
<script src="js/textures.js"></script>
<script src="js/audio.js"></script>
<script src="js/player3d.js"></script>
<script src="js/map3d.js"></script>
<script src="js/objects3d.js"></script>
<script src="js/effects3d.js"></script>
<script src="js/ui.js"></script>
<script src="js/game3d.js"></script>
</body>
</html>

386
Hommie/style.css Normal file
View File

@@ -0,0 +1,386 @@
/* СИМУЛЯТОР БОМЖА 3D - Styles */
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Roboto:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #e94560;
--secondary: #4ecdc4;
--dark: #1a1a2e;
--darker: #0f0f23;
--gold: #f39c12;
}
body {
background: linear-gradient(135deg, var(--dark) 0%, #16213e 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Roboto', sans-serif;
color: #fff;
overflow: hidden;
}
#game-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
/* Header */
#game-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 900px;
padding: 15px 25px;
background: rgba(26, 26, 46, 0.95);
border-radius: 15px;
border: 2px solid var(--primary);
box-shadow: 0 0 30px rgba(233, 69, 96, 0.3);
}
#game-header h1 {
font-family: 'Press Start 2P', cursive;
font-size: 16px;
color: var(--primary);
}
#day-time {
display: flex;
gap: 20px;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
}
#day-time span {
padding: 8px 15px;
background: rgba(0,0,0,0.4);
border-radius: 8px;
}
#day-display { color: var(--gold); }
#time-display { color: var(--secondary); }
/* Game Container */
#game-container {
position: relative;
width: 900px;
height: 600px;
border-radius: 15px;
overflow: hidden;
border: 3px solid var(--primary);
box-shadow: 0 0 50px rgba(233, 69, 96, 0.4);
}
#game-canvas {
width: 100%;
height: 100%;
}
/* UI Overlay */
#ui-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
#ui-overlay > * {
pointer-events: auto;
}
.panel {
background: rgba(0, 0, 0, 0.85);
border: 2px solid var(--primary);
border-radius: 10px;
}
.panel-header {
padding: 10px 15px;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
color: var(--primary);
border-bottom: 1px solid rgba(233, 69, 96, 0.3);
}
/* Stats Panel */
#stats-panel {
position: absolute;
top: 15px;
left: 15px;
padding: 15px;
}
.stat-row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.stat-row:last-child { margin-bottom: 0; }
.stat-icon {
font-size: 16px;
margin-right: 10px;
}
.stat-bar-container {
width: 100px;
height: 16px;
background: #333;
border-radius: 8px;
overflow: hidden;
margin-right: 10px;
}
.stat-bar {
height: 100%;
border-radius: 8px;
transition: width 0.3s;
}
.health-bar { background: linear-gradient(90deg, #e74c3c, #ff6b6b); }
.energy-bar { background: linear-gradient(90deg, #4ecdc4, #45b7aa); }
.stat-value {
font-family: 'Press Start 2P', cursive;
font-size: 10px;
min-width: 50px;
}
.money-value { color: var(--gold); }
/* Location Bar */
#location-bar {
position: absolute;
top: 15px;
left: 50%;
transform: translateX(-50%);
padding: 10px 25px;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
display: flex;
align-items: center;
gap: 10px;
background: rgba(0, 0, 0, 0.85);
border: 2px solid var(--gold);
border-radius: 20px;
}
#location-icon { font-size: 18px; }
#location-name { color: var(--gold); }
/* Inventory Panel */
#inventory-panel {
position: absolute;
bottom: 15px;
left: 15px;
padding: 10px;
width: 220px;
}
#inventory-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 5px;
padding: 10px;
}
.inv-slot {
aspect-ratio: 1;
background: rgba(0, 0, 0, 0.5);
border: 2px solid #555;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
cursor: pointer;
transition: all 0.2s;
}
.inv-slot:hover { border-color: var(--primary); transform: scale(1.1); }
.inv-slot.empty { opacity: 0.3; cursor: default; }
.inv-slot.empty:hover { transform: none; border-color: #555; }
/* Locations Panel */
#locations-panel {
position: absolute;
bottom: 15px;
right: 15px;
width: 150px;
padding: 10px;
}
.loc-btn {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 10px;
margin: 5px 0;
background: rgba(0, 0, 0, 0.3);
border: 1px solid #555;
border-radius: 5px;
color: #fff;
font-family: 'Press Start 2P', cursive;
font-size: 7px;
cursor: pointer;
transition: all 0.2s;
}
.loc-btn:hover { background: var(--primary); }
.loc-btn.active { background: var(--primary); border-color: var(--gold); }
/* Action Hint */
#action-hint {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
padding: 8px 20px;
background: rgba(0, 0, 0, 0.7);
border: 1px solid #fff;
border-radius: 20px;
font-size: 10px;
opacity: 0;
transition: opacity 0.3s;
}
#action-hint.visible { opacity: 1; }
.key {
display: inline-block;
padding: 3px 8px;
background: var(--primary);
border-radius: 4px;
margin: 0 3px;
font-family: 'Press Start 2P', cursive;
font-size: 8px;
}
/* Modal */
.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.95);
padding: 25px;
border-radius: 15px;
border: 3px solid var(--primary);
text-align: center;
display: none;
z-index: 100;
max-width: 350px;
}
.modal.show { display: block; }
#message-icon {
font-size: 40px;
margin-bottom: 10px;
}
#message-text {
font-size: 12px;
line-height: 1.6;
margin-bottom: 15px;
}
.modal-btn {
padding: 12px 30px;
font-family: 'Press Start 2P', cursive;
font-size: 9px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
.modal-btn:hover { background: #ff6b6b; }
/* Title Screen */
#title-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 50;
}
#title-screen.hidden { display: none; }
.title-logo {
font-size: 60px;
animation: float 3s ease-in-out infinite;
}
#title-screen h1 {
font-family: 'Press Start 2P', cursive;
font-size: 20px;
color: var(--primary);
margin: 15px 0;
}
.subtitle {
color: #888;
margin-bottom: 30px;
}
.start-btn {
padding: 15px 50px;
font-family: 'Press Start 2P', cursive;
font-size: 11px;
background: var(--primary);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
animation: pulse 2s infinite;
}
.start-btn:hover { transform: scale(1.05); }
.controls-info {
margin-top: 30px;
display: flex;
flex-wrap: wrap;
gap: 15px;
font-size: 9px;
color: #555;
justify-content: center;
max-width: 600px;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15px); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
#game-footer {
font-size: 10px;
color: rgba(255,255,255,0.3);
}