1134 lines
44 KiB
JavaScript
1134 lines
44 KiB
JavaScript
export class UI {
|
||
constructor(game) {
|
||
this.game = game;
|
||
this.dialogCallback = null;
|
||
this.inventoryOpen = false;
|
||
this.questsOpen = false;
|
||
this.skillsOpen = false;
|
||
this.achievementsOpen = false;
|
||
this.minimapCtx = null;
|
||
this.begProgressEl = null;
|
||
this.buskProgressEl = null;
|
||
this.tooltipEl = null;
|
||
}
|
||
|
||
init() {
|
||
document.getElementById('hud').classList.remove('hidden');
|
||
|
||
// Close buttons
|
||
document.getElementById('btn-close-inv').addEventListener('click', () => this.toggleInventory());
|
||
document.getElementById('btn-close-quest').addEventListener('click', () => this.toggleQuests());
|
||
document.getElementById('btn-close-skills').addEventListener('click', () => this.toggleSkills());
|
||
document.getElementById('btn-close-achievements').addEventListener('click', () => this.toggleAchievements());
|
||
|
||
// Hotbar clicks
|
||
const hotbarItems = document.querySelectorAll('.hotbar-item');
|
||
if (hotbarItems[0]) hotbarItems[0].addEventListener('click', () => this.toggleInventory());
|
||
if (hotbarItems[1]) hotbarItems[1].addEventListener('click', () => this.toggleQuests());
|
||
if (hotbarItems[2]) hotbarItems[2].addEventListener('click', () => this.toggleSkills());
|
||
if (hotbarItems[3]) hotbarItems[3].addEventListener('click', () => this.toggleAchievements());
|
||
|
||
// Minimap
|
||
const minimap = document.getElementById('minimap');
|
||
this.minimapCtx = minimap.getContext('2d');
|
||
|
||
// Tooltip
|
||
this.tooltipEl = document.getElementById('tooltip');
|
||
|
||
// Beg progress bar
|
||
this.createBegProgress();
|
||
this.createBuskProgress();
|
||
}
|
||
|
||
createBegProgress() {
|
||
const el = document.createElement('div');
|
||
el.id = 'beg-progress';
|
||
el.classList.add('hidden');
|
||
el.innerHTML = `
|
||
<div class="beg-label">Попрошайничество...</div>
|
||
<div class="beg-bar-bg"><div class="beg-bar-fill"></div></div>
|
||
`;
|
||
document.body.appendChild(el);
|
||
this.begProgressEl = el;
|
||
}
|
||
|
||
createBuskProgress() {
|
||
const el = document.createElement('div');
|
||
el.id = 'busk-progress';
|
||
el.classList.add('hidden');
|
||
el.innerHTML = `
|
||
<div class="beg-label">Играю на гармошке...</div>
|
||
<div class="beg-bar-bg"><div class="beg-bar-fill" style="background:#e8a020"></div></div>
|
||
`;
|
||
document.body.appendChild(el);
|
||
this.buskProgressEl = el;
|
||
}
|
||
|
||
update(dt) {
|
||
const stats = this.game.player.stats;
|
||
|
||
// Stat bars
|
||
this.updateBar('health', stats.health);
|
||
this.updateBar('hunger', stats.hunger);
|
||
this.updateBar('warmth', stats.warmth);
|
||
this.updateBar('mood', stats.mood);
|
||
this.updateBar('hygiene', stats.hygiene);
|
||
|
||
// Critical indicators
|
||
this.setCritical('health', stats.health < 20);
|
||
this.setCritical('hunger', stats.hunger < 15);
|
||
this.setCritical('warmth', stats.warmth < 20);
|
||
this.setCritical('mood', stats.mood < 10);
|
||
this.setCritical('hygiene', stats.hygiene < 20);
|
||
|
||
// Money
|
||
document.getElementById('val-money').textContent = Math.floor(stats.money);
|
||
|
||
// Time
|
||
document.getElementById('val-time').textContent = this.game.getTimeString();
|
||
document.getElementById('val-day').textContent = `День ${this.game.gameDay}`;
|
||
|
||
// Weather + season
|
||
document.getElementById('val-weather').textContent = this.game.weather.getIcon();
|
||
document.getElementById('val-temp').textContent = `${Math.round(this.game.weather.temperature)}°C`;
|
||
document.getElementById('val-season').textContent = `${this.game.seasons.getIcon()} ${this.game.seasons.getName()}`;
|
||
|
||
// Protection & warmth bonuses
|
||
const protEl = document.getElementById('val-protection');
|
||
const warmthBonusEl = document.getElementById('val-warmth-bonus');
|
||
if (protEl) protEl.textContent = this.game.equipment.getProtectionBonus();
|
||
if (warmthBonusEl) warmthBonusEl.textContent = this.game.equipment.getWarmthBonus();
|
||
|
||
// Stamina
|
||
this.updateStaminaBar();
|
||
|
||
// Reputation
|
||
this.updateReputationDisplay();
|
||
|
||
// Dog
|
||
this.updateDogIndicator();
|
||
|
||
// Danger
|
||
this.updateDangerWarning();
|
||
|
||
// Compass
|
||
this.updateCompass();
|
||
|
||
// Equipment HUD
|
||
this.updateEquipmentHUD();
|
||
|
||
// Quest Tracker
|
||
this.updateQuestTracker();
|
||
|
||
// Minimap (скрыть внутри зданий)
|
||
const minimapCanvas = document.getElementById('minimap');
|
||
if (minimapCanvas) {
|
||
minimapCanvas.style.display = this.game.interiors?.isInside ? 'none' : 'block';
|
||
}
|
||
if (!this.game.interiors?.isInside) {
|
||
this.renderMinimap();
|
||
}
|
||
}
|
||
|
||
updateStaminaBar() {
|
||
let bar = document.getElementById('stamina-bar');
|
||
if (!bar) {
|
||
bar = document.createElement('div');
|
||
bar.id = 'stamina-bar';
|
||
bar.innerHTML = '<div class="stamina-fill"></div>';
|
||
document.getElementById('hud').appendChild(bar);
|
||
}
|
||
const fill = bar.querySelector('.stamina-fill');
|
||
const pct = this.game.player.stamina / this.game.player.maxStamina * 100;
|
||
fill.style.width = pct + '%';
|
||
bar.style.opacity = pct < 100 ? '1' : '0';
|
||
}
|
||
|
||
updateDogIndicator() {
|
||
if (!this.game.dog.adopted) return;
|
||
let el = document.getElementById('dog-indicator');
|
||
if (!el) {
|
||
el = document.createElement('div');
|
||
el.id = 'dog-indicator';
|
||
el.style.cssText = 'position:absolute;bottom:68px;right:15px;background:linear-gradient(135deg,rgba(10,10,20,0.85),rgba(20,20,35,0.75));padding:4px 12px;border-radius:8px;font-size:0.75rem;color:#c8a040;pointer-events:none;backdrop-filter:blur(8px);border:1px solid rgba(200,160,64,0.15);';
|
||
document.getElementById('hud').appendChild(el);
|
||
}
|
||
el.textContent = '🐕 Шарик рядом';
|
||
}
|
||
|
||
updateReputationDisplay() {
|
||
const el = document.getElementById('reputation-display');
|
||
if (!el) return;
|
||
const rep = this.game.reputation;
|
||
el.style.color = rep.getColor();
|
||
el.textContent = `⭐ ${rep.getLevel()} (${rep.value > 0 ? '+' : ''}${rep.value})`;
|
||
}
|
||
|
||
updateDangerWarning() {
|
||
let el = document.getElementById('danger-warning');
|
||
if (this.game.dangers.hasNearbyDanger()) {
|
||
if (!el) {
|
||
el = document.createElement('div');
|
||
el.id = 'danger-warning';
|
||
el.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) translateY(-80px);color:#f44336;font-size:0.9rem;font-weight:700;pointer-events:none;text-shadow:0 0 15px rgba(244,67,54,0.5);animation:criticalPulse 1s infinite;padding:8px 20px;background:linear-gradient(135deg,rgba(60,0,0,0.8),rgba(40,0,0,0.7));border-radius:8px;border:1px solid rgba(244,67,54,0.3);backdrop-filter:blur(8px);';
|
||
document.getElementById('hud').appendChild(el);
|
||
}
|
||
el.textContent = '⚠ ОПАСНОСТЬ! [Space] — отбиться';
|
||
el.style.display = 'block';
|
||
} else if (el) {
|
||
el.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
updateEquipmentHUD() {
|
||
let el = document.getElementById('equipment-hud');
|
||
if (!el) {
|
||
el = document.createElement('div');
|
||
el.id = 'equipment-hud';
|
||
el.style.cssText = 'position:absolute;bottom:15px;right:15px;background:linear-gradient(135deg,rgba(10,10,20,0.85),rgba(20,20,35,0.75));padding:6px 10px;border-radius:8px;font-size:0.7rem;pointer-events:none;backdrop-filter:blur(8px);display:flex;gap:8px;border:1px solid rgba(255,255,255,0.05);';
|
||
document.getElementById('hud').appendChild(el);
|
||
}
|
||
|
||
const eq = this.game.equipment;
|
||
const slotIcons = { head: '🧢', body: '🧥', feet: '🥾', hands: '🧤' };
|
||
let html = '';
|
||
for (const [slot, defaultIcon] of Object.entries(slotIcons)) {
|
||
const item = eq.getEquipped(slot);
|
||
const icon = item ? item.icon : defaultIcon;
|
||
const opacity = item ? '1' : '0.25';
|
||
html += `<span style="opacity:${opacity};font-size:1.1rem;transition:opacity 0.3s;" title="${item ? item.name : 'Пусто'}">${icon}</span>`;
|
||
}
|
||
el.innerHTML = html;
|
||
}
|
||
|
||
updateQuestTracker() {
|
||
const tracker = document.getElementById('quest-tracker');
|
||
if (!tracker) return;
|
||
|
||
const active = this.game.questSystem.getActiveQuests();
|
||
if (active.length === 0) {
|
||
tracker.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
tracker.style.display = 'block';
|
||
const quest = active[0]; // Show first active quest
|
||
const pct = Math.min(100, (quest.progress / quest.target) * 100);
|
||
|
||
document.getElementById('tracker-title').textContent = quest.title;
|
||
document.getElementById('tracker-progress').textContent = `${Math.min(quest.progress, quest.target)} / ${quest.target}`;
|
||
document.getElementById('tracker-fill').style.width = `${pct}%`;
|
||
}
|
||
|
||
// === Job Progress ===
|
||
updateJobProgress(progress, name) {
|
||
let el = document.getElementById('job-progress');
|
||
if (!el) {
|
||
el = document.createElement('div');
|
||
el.id = 'job-progress';
|
||
el.innerHTML = `
|
||
<div class="job-label"></div>
|
||
<div class="job-bar-bg"><div class="job-bar-fill"></div></div>
|
||
<div class="job-cancel">[H] — отмена</div>
|
||
`;
|
||
el.style.cssText = 'position:fixed;bottom:140px;left:50%;transform:translateX(-50%);z-index:12;width:220px;pointer-events:none;text-align:center;';
|
||
el.querySelector('.job-label').style.cssText = 'font-size:0.8rem;color:#8fc;margin-bottom:4px;font-weight:600;';
|
||
el.querySelector('.job-bar-bg').style.cssText = 'height:5px;background:rgba(255,255,255,0.06);border-radius:3px;overflow:hidden;';
|
||
el.querySelector('.job-bar-fill').style.cssText = 'height:100%;background:linear-gradient(90deg,#43a047,#66bb6a);border-radius:3px;transition:width 0.15s;';
|
||
el.querySelector('.job-cancel').style.cssText = 'font-size:0.6rem;color:#666;margin-top:4px;';
|
||
document.body.appendChild(el);
|
||
}
|
||
el.style.display = 'block';
|
||
el.querySelector('.job-label').textContent = `🔧 ${name}`;
|
||
el.querySelector('.job-bar-fill').style.width = `${Math.min(100, progress * 100)}%`;
|
||
}
|
||
|
||
hideJobProgress() {
|
||
const el = document.getElementById('job-progress');
|
||
if (el) el.style.display = 'none';
|
||
}
|
||
|
||
updateBar(name, value) {
|
||
const bar = document.getElementById(`bar-${name}`);
|
||
const val = document.getElementById(`val-${name}`);
|
||
if (bar) bar.style.width = `${Math.max(0, Math.min(100, value))}%`;
|
||
if (val) val.textContent = Math.floor(value);
|
||
}
|
||
|
||
setCritical(name, isCritical) {
|
||
const bars = document.querySelectorAll('.stat-bar');
|
||
const names = ['health', 'hunger', 'warmth', 'mood'];
|
||
const idx = names.indexOf(name);
|
||
if (idx >= 0 && bars[idx]) {
|
||
if (isCritical) {
|
||
bars[idx].classList.add('critical');
|
||
} else {
|
||
bars[idx].classList.remove('critical');
|
||
}
|
||
}
|
||
}
|
||
|
||
updateCompass() {
|
||
const dir = document.getElementById('compass-dir');
|
||
if (!dir) return;
|
||
|
||
const yaw = this.game.cameraController.yaw;
|
||
const deg = ((yaw * 180 / Math.PI) % 360 + 360) % 360;
|
||
|
||
let label;
|
||
if (deg > 315 || deg <= 45) label = 'С';
|
||
else if (deg > 45 && deg <= 135) label = 'З';
|
||
else if (deg > 135 && deg <= 225) label = 'Ю';
|
||
else label = 'В';
|
||
|
||
dir.textContent = label;
|
||
}
|
||
|
||
updateInteractionHint(interactable) {
|
||
const hint = document.getElementById('interaction-hint');
|
||
const text = document.getElementById('hint-text');
|
||
|
||
if (interactable) {
|
||
hint.classList.remove('hidden');
|
||
text.textContent = interactable.label;
|
||
} else {
|
||
hint.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// === Tooltip ===
|
||
showTooltip(x, y, title, desc, statsHtml) {
|
||
const tt = this.tooltipEl;
|
||
if (!tt) return;
|
||
document.getElementById('tooltip-title').textContent = title;
|
||
document.getElementById('tooltip-desc').textContent = desc;
|
||
document.getElementById('tooltip-stats').innerHTML = statsHtml || '';
|
||
|
||
tt.classList.remove('hidden');
|
||
tt.style.left = Math.min(x + 12, window.innerWidth - 260) + 'px';
|
||
tt.style.top = Math.min(y - 10, window.innerHeight - 150) + 'px';
|
||
}
|
||
|
||
hideTooltip() {
|
||
if (this.tooltipEl) this.tooltipEl.classList.add('hidden');
|
||
}
|
||
|
||
// === Minimap ===
|
||
renderMinimap() {
|
||
const ctx = this.minimapCtx;
|
||
if (!ctx) return;
|
||
|
||
const w = 180, h = 180;
|
||
const scale = 0.9; // Уменьшили масштаб чтобы больше карты видно
|
||
const player = this.game.player;
|
||
|
||
ctx.clearRect(0, 0, w, h);
|
||
|
||
// Background
|
||
const bgGrad = ctx.createRadialGradient(w/2, h/2, 0, w/2, h/2, w/2);
|
||
bgGrad.addColorStop(0, '#1a1a24');
|
||
bgGrad.addColorStop(1, '#111118');
|
||
ctx.fillStyle = bgGrad;
|
||
ctx.beginPath();
|
||
ctx.arc(w / 2, h / 2, w / 2, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.arc(w / 2, h / 2, w / 2 - 1, 0, Math.PI * 2);
|
||
ctx.clip();
|
||
|
||
const cx = w / 2;
|
||
const cy = h / 2;
|
||
const px = player.position.x;
|
||
const pz = player.position.z;
|
||
|
||
// Дороги из конфига
|
||
const roads = this.game.world.mapConfig?.roads || [];
|
||
ctx.fillStyle = '#2a2a34';
|
||
roads.forEach(road => {
|
||
const rx = road.x ?? 0;
|
||
const rz = road.z ?? 0;
|
||
const rw = road.width ?? 100;
|
||
const rh = road.height ?? 8;
|
||
const isRotated = (road.rotation || 0) > 0.5; // NS road
|
||
if (isRotated) {
|
||
// NS road: width becomes vertical extent, height becomes horizontal width
|
||
const roadX = cx + (rx - px) * scale - (rh / 2) * scale;
|
||
const roadY = cy + (rz - pz) * scale - (rw / 2) * scale;
|
||
ctx.fillRect(roadX, roadY, rh * scale, rw * scale);
|
||
} else {
|
||
// EW road: normal
|
||
const roadX = cx + (rx - px) * scale - (rw / 2) * scale;
|
||
const roadY = cy + (rz - pz) * scale - (rh / 2) * scale;
|
||
ctx.fillRect(roadX, roadY, rw * scale, rh * scale);
|
||
}
|
||
});
|
||
|
||
// Здания
|
||
ctx.fillStyle = '#3a3a48';
|
||
this.game.world.buildingRects.forEach(b => {
|
||
const bx = cx + (b.x - px) * scale - (b.w / 2) * scale;
|
||
const by = cy + (b.z - pz) * scale - (b.d / 2) * scale;
|
||
ctx.fillRect(bx, by, b.w * scale, b.d * scale);
|
||
});
|
||
|
||
// Парк
|
||
const parkCfg = this.game.world.mapConfig?.structures?.park || {};
|
||
ctx.fillStyle = '#1a4a1a';
|
||
ctx.beginPath();
|
||
ctx.arc(cx + ((parkCfg.x ?? -30) - px) * scale, cy + ((parkCfg.z ?? 25) - pz) * scale, (parkCfg.radius ?? 18) * scale, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Укрытие
|
||
const shelCfg = this.game.world.mapConfig?.structures?.shelter || {};
|
||
ctx.fillStyle = '#5a4a3a';
|
||
const shelX = cx + ((shelCfg.x ?? -35) - px) * scale - 4 * scale;
|
||
const shelZ = cy + ((shelCfg.z ?? 35) - pz) * scale - 3 * scale;
|
||
this.roundRect(ctx, shelX, shelZ, 8 * scale, 6 * scale, 2);
|
||
|
||
// Больница (белый квадрат с красной точкой, БЕЗ креста)
|
||
const hospCfg = this.game.world.mapConfig?.structures?.hospital || {};
|
||
ctx.fillStyle = '#dddddd';
|
||
const hospX = cx + ((hospCfg.x ?? -45) - px) * scale - 6 * scale;
|
||
const hospZ = cy + ((hospCfg.z ?? -55) - pz) * scale - 5 * scale;
|
||
this.roundRect(ctx, hospX, hospZ, 12 * scale, 10 * scale, 2);
|
||
ctx.fillStyle = '#ee3333';
|
||
ctx.beginPath();
|
||
ctx.arc(cx + ((hospCfg.x ?? -45) - px) * scale, cy + ((hospCfg.z ?? -55) - pz) * scale, 3, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Стройка (контур)
|
||
const conCfg = this.game.world.mapConfig?.structures?.construction || {};
|
||
ctx.strokeStyle = '#aa7020';
|
||
ctx.lineWidth = 1.5;
|
||
const csX = cx + ((conCfg.x ?? 70) - px) * scale - 4.5 * scale;
|
||
const csZ = cy + ((conCfg.z ?? 60) - pz) * scale - 4.5 * scale;
|
||
ctx.strokeRect(csX, csZ, 9 * scale, 9 * scale);
|
||
|
||
// Рынок
|
||
const mktCfg = this.game.world.mapConfig?.structures?.market || {};
|
||
ctx.fillStyle = '#cc7020';
|
||
const mkX = cx + ((mktCfg.x ?? 35) - px) * scale - 7 * scale;
|
||
const mkZ = cy + ((mktCfg.z ?? -55) - pz) * scale - 5 * scale;
|
||
this.roundRect(ctx, mkX, mkZ, 14 * scale, 10 * scale, 2);
|
||
|
||
// Лагерь игрока
|
||
if (this.game.housing.built) {
|
||
ctx.fillStyle = '#33aa33';
|
||
const campX = cx + (this.game.housing.position.x - px) * scale;
|
||
const campZ = cy + (this.game.housing.position.z - pz) * scale;
|
||
ctx.beginPath();
|
||
ctx.arc(campX, campZ, 4, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#55cc55';
|
||
ctx.lineWidth = 1;
|
||
ctx.stroke();
|
||
}
|
||
|
||
// NPC
|
||
this.game.npcManager.npcs.forEach(npc => {
|
||
const nx = cx + (npc.position.x - px) * scale;
|
||
const ny = cy + (npc.position.z - pz) * scale;
|
||
ctx.fillStyle = '#4488ee';
|
||
ctx.beginPath();
|
||
ctx.arc(nx, ny, 3, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
});
|
||
|
||
// Мусорки
|
||
ctx.fillStyle = '#4a7a4a';
|
||
this.game.world.interactables.forEach(obj => {
|
||
if (obj.type === 'dumpster' || obj.type === 'trashpile') {
|
||
const dx = cx + (obj.position.x - px) * scale;
|
||
const dy = cy + (obj.position.z - pz) * scale;
|
||
ctx.fillRect(dx - 1.5, dy - 1.5, 3, 3);
|
||
}
|
||
});
|
||
|
||
// Пёс
|
||
if (this.game.dog.adopted && this.game.dog.mesh) {
|
||
ctx.fillStyle = '#c8a040';
|
||
const dogX = cx + (this.game.dog.position.x - px) * scale;
|
||
const dogY = cy + (this.game.dog.position.z - pz) * scale;
|
||
ctx.beginPath();
|
||
ctx.arc(dogX, dogY, 2.5, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
}
|
||
|
||
// Враги
|
||
this.game.dangers.enemies.forEach(enemy => {
|
||
const ex = cx + (enemy.position.x - px) * scale;
|
||
const ey = cy + (enemy.position.z - pz) * scale;
|
||
ctx.fillStyle = '#f44336';
|
||
ctx.beginPath();
|
||
ctx.arc(ex, ey, 3.5, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.strokeStyle = 'rgba(244,67,54,0.4)';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.arc(ex, ey, 5, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
});
|
||
|
||
// Полиция
|
||
this.game.police.officers.forEach(officer => {
|
||
if (!officer.mesh || !officer.mesh.visible) return;
|
||
const ox = cx + (officer.position.x - px) * scale;
|
||
const oy = cy + (officer.position.z - pz) * scale;
|
||
ctx.fillStyle = '#4488ff';
|
||
ctx.beginPath();
|
||
ctx.arc(ox, oy, 3, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
if (officer.state === 'chase') {
|
||
ctx.strokeStyle = 'rgba(68,136,255,0.5)';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.arc(ox, oy, 5, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
}
|
||
});
|
||
|
||
// Доска объявлений
|
||
const jbCfg = this.game.world.mapConfig?.structures?.jobBoard || {};
|
||
ctx.fillStyle = '#4caf50';
|
||
const jbX = cx + ((jbCfg.x ?? 20) - px) * scale;
|
||
const jbZ = cy + ((jbCfg.z ?? -8) - pz) * scale;
|
||
ctx.fillRect(jbX - 2.5, jbZ - 2.5, 5, 5);
|
||
|
||
// Маркер активного квеста
|
||
const activeQuests = this.game.questSystem.quests.filter(q => !q.completed && q.location);
|
||
if (activeQuests.length > 0) {
|
||
const quest = activeQuests[0];
|
||
const qx = cx + (quest.location.x - px) * scale;
|
||
const qy = cy + (quest.location.z - pz) * scale;
|
||
const pulse = 0.8 + Math.sin(Date.now() / 300) * 0.3;
|
||
const qs = 5 * pulse;
|
||
ctx.fillStyle = '#ffdd00';
|
||
ctx.beginPath();
|
||
ctx.moveTo(qx, qy - qs);
|
||
ctx.lineTo(qx + qs * 0.6, qy);
|
||
ctx.lineTo(qx, qy + qs);
|
||
ctx.lineTo(qx - qs * 0.6, qy);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#aa8800';
|
||
ctx.lineWidth = 1;
|
||
ctx.stroke();
|
||
}
|
||
|
||
// Игрок (стрелка)
|
||
ctx.save();
|
||
ctx.translate(cx, cy);
|
||
const yaw = this.game.cameraController.yaw;
|
||
ctx.rotate(-yaw);
|
||
|
||
ctx.fillStyle = 'rgba(240,160,64,0.15)';
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, 10, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
ctx.fillStyle = '#f0a040';
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, -7);
|
||
ctx.lineTo(-4.5, 5);
|
||
ctx.lineTo(0, 3);
|
||
ctx.lineTo(4.5, 5);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
|
||
ctx.restore();
|
||
ctx.restore();
|
||
|
||
// Рамка
|
||
ctx.strokeStyle = 'rgba(240,160,64,0.3)';
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
ctx.arc(w / 2, h / 2, w / 2 - 1, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
|
||
// Стороны света
|
||
ctx.fillStyle = 'rgba(240,160,64,0.5)';
|
||
ctx.font = '8px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('С', w/2, 12);
|
||
ctx.fillText('Ю', w/2, h - 5);
|
||
ctx.fillText('З', 8, h/2 + 3);
|
||
ctx.fillText('В', w - 8, h/2 + 3);
|
||
}
|
||
|
||
roundRect(ctx, x, y, w, h, r) {
|
||
ctx.beginPath();
|
||
ctx.moveTo(x + r, y);
|
||
ctx.lineTo(x + w - r, y);
|
||
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
||
ctx.lineTo(x + w, y + h - r);
|
||
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||
ctx.lineTo(x + r, y + h);
|
||
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
||
ctx.lineTo(x, y + r);
|
||
ctx.quadraticCurveTo(x, y, x + r, y);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
}
|
||
|
||
// === Beg progress ===
|
||
showBegProgress(show) {
|
||
if (this.begProgressEl) {
|
||
if (show) {
|
||
this.begProgressEl.classList.remove('hidden');
|
||
} else {
|
||
this.begProgressEl.classList.add('hidden');
|
||
}
|
||
}
|
||
}
|
||
|
||
updateBegProgress(ratio) {
|
||
if (!this.begProgressEl) return;
|
||
const fill = this.begProgressEl.querySelector('.beg-bar-fill');
|
||
if (fill) fill.style.width = `${Math.min(100, ratio * 100)}%`;
|
||
}
|
||
|
||
// === Busk progress ===
|
||
showBuskProgress(show) {
|
||
if (this.buskProgressEl) {
|
||
if (show) {
|
||
this.buskProgressEl.classList.remove('hidden');
|
||
} else {
|
||
this.buskProgressEl.classList.add('hidden');
|
||
}
|
||
}
|
||
}
|
||
|
||
updateBuskProgress(ratio) {
|
||
if (!this.buskProgressEl) return;
|
||
const fill = this.buskProgressEl.querySelector('.beg-bar-fill');
|
||
if (fill) fill.style.width = `${Math.min(100, ratio * 100)}%`;
|
||
}
|
||
|
||
// === Dialogs ===
|
||
showDialog(speaker, text, choices, callback) {
|
||
const box = document.getElementById('dialog-box');
|
||
document.getElementById('dialog-speaker').textContent = speaker;
|
||
document.getElementById('dialog-text').textContent = text;
|
||
|
||
const choicesEl = document.getElementById('dialog-choices');
|
||
choicesEl.innerHTML = '';
|
||
|
||
this.game.sound.playDialogOpen();
|
||
|
||
choices.forEach((choice, i) => {
|
||
const btn = document.createElement('button');
|
||
btn.className = 'dialog-choice';
|
||
btn.textContent = choice;
|
||
btn.addEventListener('click', () => callback(i));
|
||
choicesEl.appendChild(btn);
|
||
});
|
||
|
||
box.classList.remove('hidden');
|
||
this.dialogCallback = callback;
|
||
document.exitPointerLock();
|
||
}
|
||
|
||
hideDialog() {
|
||
document.getElementById('dialog-box').classList.add('hidden');
|
||
this.dialogCallback = null;
|
||
}
|
||
|
||
// === Inventory ===
|
||
toggleInventory() {
|
||
this.inventoryOpen = !this.inventoryOpen;
|
||
const screen = document.getElementById('inventory-screen');
|
||
|
||
if (this.inventoryOpen) {
|
||
this.renderInventory();
|
||
screen.classList.remove('hidden');
|
||
document.exitPointerLock();
|
||
} else {
|
||
screen.classList.add('hidden');
|
||
this.hideTooltip();
|
||
}
|
||
}
|
||
|
||
renderInventory() {
|
||
const container = document.querySelector('.inventory-content');
|
||
|
||
// Equipment section
|
||
this.renderEquipmentSection(container);
|
||
|
||
const grid = document.getElementById('inventory-grid');
|
||
grid.innerHTML = '';
|
||
|
||
const items = this.game.inventory.getAll();
|
||
|
||
items.forEach(item => {
|
||
const slot = document.createElement('div');
|
||
slot.className = 'inv-slot';
|
||
|
||
const isEquippable = item.equippable;
|
||
const isUsable = item.usable;
|
||
|
||
if (isEquippable) slot.classList.add('equippable');
|
||
|
||
slot.innerHTML = `
|
||
<span>${item.icon}</span>
|
||
<span class="item-name">${item.name}</span>
|
||
${item.count > 1 ? `<span class="item-count">x${item.count}</span>` : ''}
|
||
`;
|
||
|
||
// Tooltip on hover
|
||
slot.addEventListener('mouseenter', (e) => {
|
||
let statsHtml = '';
|
||
if (item.equippable) {
|
||
const eqData = this.game.equipment.allItems[item.key.replace('eq_', '')];
|
||
if (eqData) {
|
||
statsHtml = `🔥 +${eqData.warmth} Тепло<br>🛡️ +${eqData.protection} Защита<br>😊 +${eqData.mood} Настроение`;
|
||
}
|
||
}
|
||
if (item.usable && !item.equippable) {
|
||
statsHtml = 'Нажмите чтобы использовать';
|
||
}
|
||
this.showTooltip(e.clientX, e.clientY, item.name, item.desc || '', statsHtml);
|
||
});
|
||
slot.addEventListener('mouseleave', () => this.hideTooltip());
|
||
slot.addEventListener('mousemove', (e) => {
|
||
if (this.tooltipEl && !this.tooltipEl.classList.contains('hidden')) {
|
||
this.tooltipEl.style.left = Math.min(e.clientX + 12, window.innerWidth - 260) + 'px';
|
||
this.tooltipEl.style.top = Math.min(e.clientY - 10, window.innerHeight - 150) + 'px';
|
||
}
|
||
});
|
||
|
||
if (isEquippable || isUsable) {
|
||
slot.style.cursor = 'pointer';
|
||
slot.addEventListener('click', () => {
|
||
this.game.inventory.useItem(item.key);
|
||
this.renderInventory();
|
||
});
|
||
}
|
||
|
||
grid.appendChild(slot);
|
||
});
|
||
|
||
const empty = this.game.inventory.maxSlots - items.length;
|
||
for (let i = 0; i < Math.max(0, empty); i++) {
|
||
const slot = document.createElement('div');
|
||
slot.className = 'inv-slot';
|
||
slot.style.opacity = '0.3';
|
||
grid.appendChild(slot);
|
||
}
|
||
|
||
// Crafting section
|
||
this.renderCrafting(container);
|
||
}
|
||
|
||
renderEquipmentSection(container) {
|
||
let eqSection = container.querySelector('.equipment-section');
|
||
if (!eqSection) {
|
||
eqSection = document.createElement('div');
|
||
eqSection.className = 'equipment-section';
|
||
container.insertBefore(eqSection, document.getElementById('inventory-grid'));
|
||
}
|
||
|
||
const eq = this.game.equipment;
|
||
const slotNames = {
|
||
head: { label: 'Голова', icon: '🧢' },
|
||
body: { label: 'Тело', icon: '🧥' },
|
||
feet: { label: 'Ноги', icon: '🥾' },
|
||
hands: { label: 'Руки', icon: '🧤' }
|
||
};
|
||
|
||
let html = '<div class="eq-title">Экипировка</div><div class="eq-slots">';
|
||
|
||
for (const [slot, info] of Object.entries(slotNames)) {
|
||
const item = eq.getEquipped(slot);
|
||
const isEmpty = !item;
|
||
html += `
|
||
<div class="eq-slot ${isEmpty ? 'empty' : ''}" data-slot="${slot}">
|
||
<span class="eq-icon">${item ? item.icon : info.icon}</span>
|
||
<span class="eq-name">${item ? item.name : info.label}</span>
|
||
${item ? `<span class="eq-stats">+${item.warmth}🔥 +${item.protection}🛡️</span>` : ''}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += '</div>';
|
||
|
||
// Total bonuses
|
||
const warmth = eq.getWarmthBonus();
|
||
const protection = eq.getProtectionBonus();
|
||
const mood = eq.getMoodBonus();
|
||
if (warmth > 0 || protection > 0 || mood > 0) {
|
||
html += `<div class="eq-totals">`;
|
||
if (warmth > 0) html += `+${warmth} 🔥 `;
|
||
if (protection > 0) html += `+${protection}% 🛡️ `;
|
||
if (mood > 0) html += `+${mood} 😊`;
|
||
html += '</div>';
|
||
}
|
||
|
||
eqSection.innerHTML = html;
|
||
|
||
// Unequip handlers
|
||
eqSection.querySelectorAll('.eq-slot:not(.empty)').forEach(slotEl => {
|
||
slotEl.addEventListener('click', () => {
|
||
const slotKey = slotEl.dataset.slot;
|
||
eq.unequip(slotKey);
|
||
this.renderInventory();
|
||
});
|
||
});
|
||
}
|
||
|
||
renderCrafting(container) {
|
||
let craftSection = container.querySelector('.craft-section');
|
||
if (craftSection) craftSection.remove();
|
||
|
||
craftSection = document.createElement('div');
|
||
craftSection.className = 'craft-section';
|
||
craftSection.innerHTML = '<h3 style="color:#f0a040;margin:14px 0 10px;font-size:0.8rem;text-transform:uppercase;letter-spacing:0.05em;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.06);">Крафт</h3>';
|
||
|
||
const recipes = this.game.inventory.recipes;
|
||
const inv = this.game.inventory;
|
||
|
||
recipes.forEach(recipe => {
|
||
const canCraft = inv.canCraft(recipe);
|
||
const el = document.createElement('div');
|
||
el.className = `craft-item ${canCraft ? 'available' : ''}`;
|
||
|
||
// Build ingredients line with "has/needs" info
|
||
let ingredientsHtml = '';
|
||
if (recipe.ingredients) {
|
||
const parts = [];
|
||
for (const [key, need] of Object.entries(recipe.ingredients)) {
|
||
const have = inv.getCount(key);
|
||
const itemData = inv.itemData[key];
|
||
const name = itemData ? itemData.name : key;
|
||
const cls = have >= need ? 'has' : 'missing';
|
||
parts.push(`<span class="${cls}">${name}: ${have}/${need}</span>`);
|
||
}
|
||
ingredientsHtml = `<div class="craft-ingredients">${parts.join(' ')}</div>`;
|
||
}
|
||
|
||
el.innerHTML = `
|
||
<div style="flex:1;">
|
||
<div style="font-size:0.8rem;color:${canCraft ? '#ddd' : '#555'};font-weight:600;">${inv.itemData[recipe.result]?.icon || ''} ${recipe.name}</div>
|
||
<div style="font-size:0.65rem;color:#666;margin-top:2px;">${recipe.desc}</div>
|
||
${ingredientsHtml}
|
||
</div>
|
||
<button class="craft-btn" ${canCraft ? '' : 'disabled'}>Создать</button>
|
||
`;
|
||
|
||
if (canCraft) {
|
||
el.querySelector('.craft-btn').addEventListener('click', () => {
|
||
inv.craft(recipe);
|
||
this.renderInventory();
|
||
});
|
||
}
|
||
|
||
craftSection.appendChild(el);
|
||
});
|
||
|
||
const closeBtn = container.querySelector('.panel-close');
|
||
container.appendChild(craftSection);
|
||
}
|
||
|
||
// === Quests ===
|
||
toggleQuests() {
|
||
this.questsOpen = !this.questsOpen;
|
||
const screen = document.getElementById('quest-screen');
|
||
|
||
if (this.questsOpen) {
|
||
this.renderQuests();
|
||
screen.classList.remove('hidden');
|
||
document.exitPointerLock();
|
||
} else {
|
||
screen.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
renderQuests() {
|
||
const list = document.getElementById('quest-list');
|
||
list.innerHTML = '';
|
||
|
||
const active = this.game.questSystem.getActiveQuests();
|
||
const completed = this.game.questSystem.getCompletedQuests();
|
||
|
||
if (active.length === 0 && completed.length === 0) {
|
||
list.innerHTML = '<p style="color:#555;text-align:center;padding:20px;">Нет активных квестов</p>';
|
||
return;
|
||
}
|
||
|
||
active.forEach(quest => {
|
||
const pct = Math.min(100, (quest.progress / quest.target) * 100);
|
||
const el = document.createElement('div');
|
||
el.className = 'quest-item active';
|
||
el.innerHTML = `
|
||
<div class="quest-title">📌 ${quest.title}</div>
|
||
<div class="quest-desc">${quest.description}</div>
|
||
<div class="quest-progress">${Math.min(quest.progress, quest.target)} / ${quest.target}</div>
|
||
<div class="quest-progress-bar"><div class="quest-progress-fill" style="width:${pct}%"></div></div>
|
||
`;
|
||
list.appendChild(el);
|
||
});
|
||
|
||
if (completed.length > 0) {
|
||
const divider = document.createElement('div');
|
||
divider.style.cssText = 'font-size:0.7rem;color:#444;text-transform:uppercase;letter-spacing:0.1em;margin:16px 0 8px;padding-bottom:6px;border-bottom:1px solid rgba(255,255,255,0.04);';
|
||
divider.textContent = 'Выполнено';
|
||
list.appendChild(divider);
|
||
}
|
||
|
||
completed.forEach(quest => {
|
||
const el = document.createElement('div');
|
||
el.className = 'quest-item completed';
|
||
el.innerHTML = `
|
||
<div class="quest-title">✅ ${quest.title}</div>
|
||
<div class="quest-desc">${quest.description}</div>
|
||
`;
|
||
list.appendChild(el);
|
||
});
|
||
}
|
||
|
||
// === Skills ===
|
||
toggleSkills() {
|
||
this.skillsOpen = !this.skillsOpen;
|
||
const screen = document.getElementById('skills-screen');
|
||
|
||
if (this.skillsOpen) {
|
||
this.renderSkills();
|
||
screen.classList.remove('hidden');
|
||
document.exitPointerLock();
|
||
} else {
|
||
screen.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
renderSkills() {
|
||
const list = document.getElementById('skills-list');
|
||
list.innerHTML = '';
|
||
|
||
const icons = {
|
||
scavenging: '🔍',
|
||
begging: '🗣️',
|
||
survival: '🏕️',
|
||
trading: '💰'
|
||
};
|
||
|
||
const descs = {
|
||
scavenging: 'Больше находок при обыске',
|
||
begging: 'Больше денег от милостыни',
|
||
survival: 'Медленнее теряете тепло и сытость',
|
||
trading: 'Лучшие цены при работе'
|
||
};
|
||
|
||
const skills = this.game.skills.skills;
|
||
for (const [key, skill] of Object.entries(skills)) {
|
||
const xpPct = Math.min(100, (skill.xp / skill.xpNeeded) * 100);
|
||
const maxed = skill.level >= this.game.skills.maxLevel;
|
||
const el = document.createElement('div');
|
||
el.className = 'skill-item';
|
||
el.innerHTML = `
|
||
<div class="skill-header">
|
||
<span class="skill-name">${icons[key] || ''} ${skill.name}</span>
|
||
<span class="skill-level">${maxed ? '✨ MAX' : `Ур. ${skill.level}`}</span>
|
||
</div>
|
||
<div class="skill-desc">${skill.desc || descs[key] || ''}</div>
|
||
${!maxed ? `
|
||
<div class="skill-xp-bar"><div class="skill-xp-fill" style="width:${xpPct}%"></div></div>
|
||
<div class="skill-xp-text">${skill.xp} / ${skill.xpNeeded} XP</div>
|
||
` : ''}
|
||
`;
|
||
list.appendChild(el);
|
||
}
|
||
}
|
||
|
||
// === Achievements ===
|
||
toggleAchievements() {
|
||
this.achievementsOpen = !this.achievementsOpen;
|
||
const screen = document.getElementById('achievements-screen');
|
||
|
||
if (this.achievementsOpen) {
|
||
this.renderAchievements();
|
||
screen.classList.remove('hidden');
|
||
document.exitPointerLock();
|
||
} else {
|
||
screen.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
renderAchievements() {
|
||
const list = document.getElementById('achievements-list');
|
||
list.innerHTML = '';
|
||
|
||
const achievements = this.game.achievements;
|
||
const progress = achievements.getProgress();
|
||
|
||
// Progress header
|
||
const headerEl = document.createElement('div');
|
||
headerEl.className = 'ach-progress-header';
|
||
headerEl.innerHTML = `
|
||
<div class="ach-progress-text">Разблокировано: <span class="ach-progress-count">${progress.unlocked} / ${progress.total}</span></div>
|
||
<div class="ach-progress-bar">
|
||
<div class="ach-progress-fill" style="width:${(progress.unlocked / progress.total * 100)}%;"></div>
|
||
</div>
|
||
`;
|
||
list.appendChild(headerEl);
|
||
|
||
const categories = {
|
||
survival: { name: 'Выживание', icon: '🏕️' },
|
||
social: { name: 'Социальные', icon: '🤝' },
|
||
economy: { name: 'Экономика', icon: '💰' },
|
||
combat: { name: 'Боевые', icon: '⚔️' },
|
||
explore: { name: 'Исследование', icon: '🗺️' }
|
||
};
|
||
|
||
for (const [catKey, catInfo] of Object.entries(categories)) {
|
||
const catAchievements = achievements.getByCategory(catKey);
|
||
if (catAchievements.length === 0) continue;
|
||
|
||
const catEl = document.createElement('div');
|
||
catEl.className = 'ach-category';
|
||
|
||
const unlockedCount = catAchievements.filter(a => achievements.unlocked.has(a.id)).length;
|
||
catEl.innerHTML = `<div class="ach-category-title">${catInfo.icon} ${catInfo.name} <span style="float:right;color:#555;font-weight:400;">${unlockedCount}/${catAchievements.length}</span></div>`;
|
||
|
||
catAchievements.forEach(ach => {
|
||
const unlocked = achievements.unlocked.has(ach.id);
|
||
const achEl = document.createElement('div');
|
||
achEl.className = `ach-item ${unlocked ? 'unlocked' : 'locked'}`;
|
||
achEl.innerHTML = `
|
||
<span class="ach-icon">${unlocked ? ach.icon : '🔒'}</span>
|
||
<div class="ach-info">
|
||
<div class="ach-title">${unlocked ? ach.title : '???'}</div>
|
||
<div class="ach-desc">${ach.desc}</div>
|
||
</div>
|
||
`;
|
||
catEl.appendChild(achEl);
|
||
});
|
||
|
||
list.appendChild(catEl);
|
||
}
|
||
}
|
||
|
||
// === Intro ===
|
||
showIntro(callback) {
|
||
const overlay = document.getElementById('intro-overlay');
|
||
const textEl = document.getElementById('intro-text');
|
||
overlay.classList.remove('hidden');
|
||
|
||
const lines = [
|
||
'Ещё вчера у вас было всё — работа, квартира, друзья...',
|
||
'Одна ошибка, и жизнь перевернулась.',
|
||
'Теперь ваш дом — улица, а главная цель — дожить до завтра.',
|
||
'Ищите еду, собирайте бутылки, заводите знакомства.',
|
||
'Не сдавайтесь. Каждый день — это шанс.'
|
||
];
|
||
|
||
textEl.innerHTML = '';
|
||
lines.forEach((line, i) => {
|
||
const p = document.createElement('p');
|
||
p.className = 'intro-line';
|
||
p.textContent = line;
|
||
p.style.animationDelay = `${i * 1.2}s`;
|
||
textEl.appendChild(p);
|
||
});
|
||
|
||
const skipBtn = document.getElementById('btn-skip-intro');
|
||
const closeIntro = () => {
|
||
overlay.classList.add('hidden');
|
||
skipBtn.removeEventListener('click', closeIntro);
|
||
if (callback) callback();
|
||
};
|
||
|
||
skipBtn.addEventListener('click', closeIntro);
|
||
setTimeout(closeIntro, lines.length * 1200 + 2000);
|
||
}
|
||
|
||
// === Notifications ===
|
||
showNotification(text, type) {
|
||
const container = document.getElementById('notifications');
|
||
const notif = document.createElement('div');
|
||
notif.className = 'notification';
|
||
if (type === 'good') notif.classList.add('good');
|
||
if (type === 'bad') notif.classList.add('bad');
|
||
notif.textContent = text;
|
||
container.appendChild(notif);
|
||
setTimeout(() => notif.remove(), 3600);
|
||
}
|
||
|
||
// === Death ===
|
||
showDeathScreen(reason, day) {
|
||
const screen = document.getElementById('death-screen');
|
||
document.getElementById('death-reason').textContent = reason;
|
||
|
||
const money = Math.floor(this.game.player.stats.money);
|
||
const completedQuests = this.game.questSystem.getCompletedQuests().length;
|
||
const totalQuests = this.game.questSystem.quests.length;
|
||
const hasDog = this.game.dog.adopted;
|
||
const skills = this.game.skills.skills;
|
||
const totalLevels = Object.values(skills).reduce((s, sk) => s + sk.level, 0);
|
||
const repLevel = this.game.reputation.getLevel();
|
||
const achProgress = this.game.achievements.getProgress();
|
||
const eqSlots = this.game.equipment.getFilledSlots();
|
||
|
||
document.getElementById('death-stats').innerHTML = `
|
||
<div class="death-stat-grid">
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">📅</span>
|
||
<span class="ds-value">${day}</span>
|
||
<span class="ds-label">Дней</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">💰</span>
|
||
<span class="ds-value">${money}₽</span>
|
||
<span class="ds-label">Денег</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">📋</span>
|
||
<span class="ds-value">${completedQuests}/${totalQuests}</span>
|
||
<span class="ds-label">Квесты</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">⚡</span>
|
||
<span class="ds-value">${totalLevels}</span>
|
||
<span class="ds-label">Навыки</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">⭐</span>
|
||
<span class="ds-value">${repLevel}</span>
|
||
<span class="ds-label">Репутация</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">🏆</span>
|
||
<span class="ds-value">${achProgress.unlocked}/${achProgress.total}</span>
|
||
<span class="ds-label">Достижения</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">🛡️</span>
|
||
<span class="ds-value">${eqSlots}/4</span>
|
||
<span class="ds-label">Экипировка</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">🏠</span>
|
||
<span class="ds-value">${this.game.housing.built ? 'Да' : 'Нет'}</span>
|
||
<span class="ds-label">Укрытие</span>
|
||
</div>
|
||
<div class="death-stat-card">
|
||
<span class="ds-icon">🐕</span>
|
||
<span class="ds-value">${hasDog ? 'Да' : 'Нет'}</span>
|
||
<span class="ds-label">Пёс</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
screen.classList.remove('hidden');
|
||
|
||
document.getElementById('btn-restart').onclick = () => {
|
||
screen.classList.add('hidden');
|
||
this.game.restart();
|
||
};
|
||
}
|
||
|
||
hideDeathScreen() {
|
||
document.getElementById('death-screen').classList.add('hidden');
|
||
}
|
||
}
|