UI Razor component for the game's HUD and menus. It renders score cards, scoreboard, killfeed, hotbar, chat, spawn/workshop menus, settings and leaderboard, and wires UI actions to game systems (PushAbility, SaveManager, Leaderboards, WeaponManager, etc.).
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Services;
@using System;
@using System.Linq;
@using System.Collections.Generic;
@using System.Threading.Tasks;
@inherits PanelComponent
@attribute [StyleSheet( "PushHud.scss" )]
<root class="hud-root">
<div class="top-header-container">
<div class="score-box">
<div class="score-card time">
<label class="score-title">@L( "Время", "Time" )</label>
<label class="score-value">@GetFormattedTime()</label>
</div>
<div class="score-card">
<label class="score-title">@L( "Сбито", "Score" )</label>
<label class="score-value">@GetScore()</label>
</div>
<div class="score-card death">
<label class="score-title">@L( "Смерти", "Deaths" )</label>
<label class="score-value">@GetDeaths()</label>
</div>
<div class="score-card highlight">
<label class="score-title">@L( "Рекорд", "Best" )</label>
<label class="score-value">@GetHighScore()</label>
</div>
</div>
<div class="round-badge">
@L( "РАУНД", "ROUND" ) @(RoundManager.Instance?.CurrentRound ?? 1)
</div>
@if ( MapEventManager.Instance != null && MapEventManager.Instance.CurrentEvent != WeatherType.None )
{
<div class="weather-banner @GetWeatherClass( MapEventManager.Instance.CurrentEvent )">
<label class="weather-icon">@GetWeatherIcon( MapEventManager.Instance.CurrentEvent )</label>
<div class="weather-info">
<label class="weather-title">@GetWeatherTitle( MapEventManager.Instance.CurrentEvent )</label>
<label class="weather-sub">@L( "АКТИВНАЯ АНОМАЛИЯ", "ACTIVE ANOMALY" )</label>
</div>
<div class="weather-timer-badge">
<label class="timer-text">@($"{MapEventManager.Instance.TimeRemaining:F1}s")</label>
</div>
</div>
}
</div>
<div class="scoreboard-box">
<div class="scoreboard-header">
<label class="header-title">@L( "ИГРОКИ", "PLAYERS" )</label>
</div>
<div class="player-list">
@foreach ( var p in GetPlayerList() )
{
<div class="player-row @(p.IsLocal ? "local" : "")">
<label class="player-name">@p.Name</label>
<label class="player-score">@p.Score</label>
</div>
}
</div>
</div>
<div class="killfeed-container">
@if ( Killfeed.Instance != null )
{
@foreach ( var entry in Killfeed.Instance.Entries )
{
<div class="kill-entry">
<label class="killer">@entry.KillerName</label>
<label class="icon">💥 [@entry.Method]</label>
<label class="victim">@entry.VictimName</label>
</div>
}
}
</div>
<div class="hotbar-container">
@if ( ShopManager.Instance != null )
{
@foreach ( var item in ShopManager.Instance.Items )
{
bool isSelected = item == ShopManager.Instance.SelectedItem;
<div class="slot-wrapper">
<div class="hotbar-slot @(isSelected ? "selected" : "") @(!item.IsUnlocked ? "locked" : "")"
onclick="@(() => ShopManager.Instance.TryBuyOrSelect(item))">
<label class="slot-name">@item.GetName( CurrentLanguage )</label>
@if ( !item.IsUnlocked )
{
<label class="slot-cost">💰 @item.Cost</label>
}
</div>
<div class="key-badge @(isSelected ? "selected" : "")">@item.Key</div>
</div>
}
}
</div>
<div class="top-left-box">
<div class="chat-box @(IsChatOpen ? "open" : "")">
<div class="chat-messages">
@foreach ( var msg in CanvasMessages )
{
<div class="chat-row">
<label class="chat-author">@msg.Author:</label>
<label class="chat-text">@msg.Text</label>
</div>
}
</div>
@if ( IsChatOpen )
{
<div class="chat-input-row">
<textentry @ref="ChatInput" onsubmit="@SendChatMessage" placeholder="@L( "Введите сообщение...", "Type a message..." )"></textentry>
</div>
}
else
{
<div class="hints-row">
<label class="chat-hint" onclick="@OpenChat">@L( "Нажмите [Enter] для чата", "Press [Enter] to chat" )</label>
<label class="chat-hint prop-hint" onclick="@ToggleWorkshopMenu">@L( "[E] Спавн-меню | [R] Мастерская", "[E] Spawnmenu | [R] Workshop" )</label>
</div>
}
</div>
</div>
<div class="player-status-panel">
<div class="status-box health-box">
<div class="status-header">
<label class="status-title">❤️ @L( "ЗДОРОВЬЕ", "HEALTH" )</label>
<label class="status-val">@($"{GetHealth():F0}") / @($"{GetMaxHealth():F0}")</label>
</div>
<div class="bar-background">
<div class="bar-fill health" style="width: @($"{GetHealthPercent():F1}%");"></div>
</div>
</div>
<div class="status-box cooldown-box">
<div class="status-header">
<label class="status-title">⚡ @L( "ПЕРЕЗАРЯДКА", "COOLDOWN" )</label>
</div>
<div class="bar-background">
<div class="bar-fill" style="width: @($"{GetPercent():F1}%");"></div>
</div>
</div>
</div>
@if ( PushAbility != null && PushAbility.IsCharging )
{
<div class="charge-box">
<label class="charge-title">@L( "ЗАРЯД", "CHARGE" )</label>
<div class="bar-background">
<div class="bar-fill charge" style="width: @($"{PushAbility.ChargeProgress * 100f:F1}%");"></div>
</div>
</div>
}
@if ( LocalHealth != null && LocalHealth.IsDead )
{
<div class="respawn-overlay">
<div class="respawn-card">
<label class="death-title">💀 @L( "ВЫ ПОГИБЛИ!", "YOU DIED!" )</label>
<label class="respawn-subtitle">
@L( "Возрождение через:", "Respawning in:" )
</label>
<label class="respawn-timer">@($"{Math.Max( 0, LocalHealth.RespawnTimer ):F1}s")</label>
</div>
</div>
}
@if ( !IsMenuOpen && !IsSettingsOpen && !IsWorkshopMenuOpen && !IsQSpawnMenuOpen )
{
<button class="side-settings-btn" onclick="@OpenMenu">
<label class="key-hint">[ESC]</label>
<label class="icon">⚙</label>
</button>
}
@if ( IsQSpawnMenuOpen )
{
<div class="spawnmenu-overlay" onclick="@CloseQSpawnMenu">
<div class="spawnmenu-window" onclick="@((e) => e.StopPropagation())">
<div class="spawnmenu-topbar">
<div class="brand-badge">
<label class="brand-text">ONE MORE PUSH</label>
<label class="sub-text">SPAWN MENU</label>
</div>
<div class="topbar-nav">
<button class="topbar-tab active">📦 @L( "ПРОПСЫ И АРЕНА", "PROPS & ARENA" )</button>
<button class="topbar-tab" onclick="@ToggleWorkshopMenu">⚡ @L( "МАСТЕРСКАЯ [R]", "WORKSHOP [R]" )</button>
</div>
<div class="topbar-credits">
<label class="credits-label">@L( "БАЛАНС:", "BALANCE:" )</label>
<label class="credits-val">💰 @GetCredits()</label>
</div>
</div>
<div class="spawnmenu-body">
<div class="spawnmenu-sidebar">
<label class="sidebar-title">@L( "КАТЕГОРИИ", "CATEGORIES" )</label>
<div class="sidebar-item active">💥 @L( "Интерактив", "Interactive" )</div>
<div class="sidebar-item">🛢️ @L( "Опасные зоны", "Hazard Zones" )</div>
<div class="sidebar-item">⚓ @L( "Тяжелые объекты", "Heavy Objects" )</div>
<div class="sidebar-item">🌀 @L( "Ловушки & Батуты", "Traps & Springs" )</div>
</div>
<div class="spawnmenu-content">
<div class="content-header">
<label class="section-title">@L( "СПАВН ОБЪЕКТОВ", "SPAWN OBJECTS" )</label>
<label class="section-desc">@L( "Выберите предмет для создания на карте", "Select an item to spawn on the map" )</label>
</div>
<div class="gmod-prop-grid">
<div class="gmod-prop-card" onclick="@(() => SelectAndSpawnProp(0))">
<div class="card-glow"></div>
<div class="prop-icon">💥</div>
<label class="prop-label">@L( "Взрывная Бочка", "Explosive Barrel" )</label>
<label class="prop-price">💰 15</label>
</div>
<div class="gmod-prop-card" onclick="@(() => SelectAndSpawnProp(1))">
<div class="card-glow"></div>
<div class="prop-icon">🛢️</div>
<label class="prop-label">@L( "Зона Смолы", "Tar Zone" )</label>
<label class="prop-price">💰 20</label>
</div>
<div class="gmod-prop-card" onclick="@(() => SelectAndSpawnProp(2))">
<div class="card-glow"></div>
<div class="prop-icon">⚓</div>
<label class="prop-label">@L( "Наковальня", "Anvil" )</label>
<label class="prop-price">💰 25</label>
</div>
<div class="gmod-prop-card" onclick="@(() => SelectAndSpawnProp(3))">
<div class="card-glow"></div>
<div class="prop-icon">🌀</div>
<label class="prop-label">@L( "Пружинный Батут", "Spring Pad" )</label>
<label class="prop-price">💰 20</label>
</div>
</div>
</div>
<div class="spawnmenu-tools">
<label class="tools-title">@L( "ИНСТРУМЕНТЫ", "TOOLS" )</label>
<div class="tool-group">
<label class="group-title">@L( "Взаимодействие", "Interaction" )</label>
<div class="tool-item active">👉 @L( "Спавн-меню [E]", "Spawn Menu [E]" )</div>
<div class="tool-item">🚀 @L( "Силовая волна", "Force Wave" )</div>
</div>
<div class="tool-group">
<label class="group-title">@L( "Управление", "Controls" )</label>
<div class="tool-hint">@L( "Отпустите [E] для закрытия меню", "Release [E] to close menu" )</div>
</div>
</div>
</div>
</div>
</div>
}
@if ( IsWorkshopMenuOpen )
{
<div class="menu-overlay" onclick="@CloseWorkshopMenu">
<div class="prop-menu-card" onclick="@((e) => e.StopPropagation())">
<div class="workshop-header">
<label class="menu-title">@L( "МАСТЕРСКАЯ АРЕНЫ", "ARENA WORKSHOP" )</label>
<div class="credits-badge">💰 @GetCredits()</div>
</div>
<div class="tab-selector">
<button class="tab-btn @(ActiveTab == "PROPS" ? "active" : "")" onclick="@(() => SelectTab("PROPS"))">
📦 @L( "ПРОПСЫ", "PROPS" )
</button>
<button class="tab-btn @(ActiveTab == "UPGRADES" ? "active" : "")" onclick="@(() => SelectTab("UPGRADES"))">
⚡ @L( "ПРОКАЧКА", "UPGRADES" )
</button>
<button class="tab-btn @(ActiveTab == "WEAPONS" ? "active" : "")" onclick="@(() => SelectTab("WEAPONS"))">
🔫 @L( "ОРУЖИЕ", "WEAPONS" )
</button>
<button class="tab-btn @(ActiveTab == "PRESTIGE" ? "active" : "")" onclick="@(() => SelectTab("PRESTIGE"))">
👑 @L( "ПРЕСТИЖ", "PRESTIGE" )
</button>
</div>
@if ( ActiveTab == "PROPS" )
{
<div class="prop-grid">
<div class="prop-item" onclick="@(() => SelectAndSpawnProp(0))">
<label class="prop-name">💥 @L( "Бочка", "Barrel" )</label>
<label class="prop-cost">💰 15</label>
</div>
<div class="prop-item" onclick="@(() => SelectAndSpawnProp(1))">
<label class="prop-name">🛢️ @L( "Смола", "Tar Zone" )</label>
<label class="prop-cost">💰 20</label>
</div>
<div class="prop-item" onclick="@(() => SelectAndSpawnProp(2))">
<label class="prop-name">⚓ @L( "Наковальня", "Anvil" )</label>
<label class="prop-cost">💰 25</label>
</div>
<div class="prop-item" onclick="@(() => SelectAndSpawnProp(3))">
<label class="prop-name">🌀 @L( "Батут", "Spring" )</label>
<label class="prop-cost">💰 20</label>
</div>
</div>
}
@if ( ActiveTab == "UPGRADES" )
{
<div class="upgrades-list">
<div class="upgrade-row">
<div class="info">
<label class="name">💪 @L( "СИЛА УДАРА", "PUSH FORCE" )</label>
<label class="level">LVL @GetForceLevel()</label>
</div>
<button class="buy-btn" onclick="@TryUpgradeForce">
<label class="btn-text">@L( "КУПИТЬ", "BUY" )</label>
<label class="btn-cost">@GetForceCost() 💰</label>
</button>
</div>
<div class="upgrade-row">
<div class="info">
<label class="name">🏋️ @L( "МАССА ИГРОКА", "PLAYER MASS" )</label>
<label class="level">LVL @GetMassLevel()</label>
</div>
<button class="buy-btn" onclick="@TryUpgradeMass">
<label class="btn-text">@L( "КУПИТЬ", "BUY" )</label>
<label class="btn-cost">@GetMassCost() 💰</label>
</button>
</div>
<div class="upgrade-row">
<div class="info">
<label class="name">⚡ @L( "ЭНЕРГИЯ И РЫВОК", "ENERGY RECOVERY" )</label>
<label class="level">LVL @GetEnergyLevel()</label>
</div>
<button class="buy-btn" onclick="@TryUpgradeEnergy">
<label class="btn-text">@L( "КУПИТЬ", "BUY" )</label>
<label class="btn-cost">@GetEnergyCost() 💰</label>
</button>
</div>
</div>
}
@if ( ActiveTab == "WEAPONS" )
{
<div class="upgrades-list">
@if ( WeaponManager.Instance != null )
{
@foreach ( var w in WeaponManager.Instance.Weapons )
{
bool isEquipped = WeaponManager.Instance.CurrentWeapon == w;
<div class="upgrade-row">
<div class="info">
<label class="name">@(CurrentLanguage == "RU" ? w.NameRu : w.NameEn)</label>
<label class="level">
@(isEquipped ? L( "ЭКИПИРОВАНО", "EQUIPPED" ) : (w.IsUnlocked ? L( "КУПЛЕНО", "UNLOCKED" ) : L( "ЗАКРЫТО", "LOCKED" )))
</label>
</div>
@if ( isEquipped )
{
<button class="buy-btn equipped">
<label class="btn-text">✔ @L( "АКТИВНО", "ACTIVE" )</label>
</button>
}
else if ( w.IsUnlocked )
{
<button class="buy-btn equip" onclick="@(() => WeaponManager.Instance.TryBuyOrEquip(w))">
<label class="btn-text">⚡ @L( "ВЗЯТЬ", "EQUIP" )</label>
</button>
}
else
{
<button class="buy-btn" onclick="@(() => WeaponManager.Instance.TryBuyOrEquip(w))">
<label class="btn-text">@L( "КУПИТЬ", "BUY" )</label>
<label class="btn-cost">@w.Cost 💰</label>
</button>
}
</div>
}
}
</div>
}
@if ( ActiveTab == "PRESTIGE" )
{
<div class="prestige-card-container">
<div class="prestige-info-box">
<label class="prestige-lvl">👑 @L( "УРОВЕНЬ ПРЕСТИЖА:", "PRESTIGE LEVEL:" ) @GetPrestigeLevel()</label>
<label class="prestige-bonus">✨ @L( "БОНУС К КРЕДИТАМ:", "CREDIT BONUS:" ) +@(GetPrestigeLevel() * 50)%</label>
<label class="prestige-tokens">🔮 @L( "ОЧКИ ПРЕСТИЖА:", "PRESTIGE TOKENS:" ) @GetPrestigeTokens()</label>
</div>
<div class="prestige-desc">
<label class="desc-text">
@L( "Сбросьте уровни Силы, Массы и Энергии, чтобы получить Очко Престижа и постоянный бонус +50% к заработку!",
"Reset Force, Mass, and Energy levels to get a Prestige Token and a permanent +50% credit bonus!" )
</label>
<label class="req-text">
@L( "ТРЕБОВАНИЕ: Сумма уровней прокачки 15+", "REQUIREMENT: Total Upgrade Levels 15+" )
</label>
</div>
<button class="prestige-btn @(CanPrestige() ? "ready" : "disabled")" onclick="@TryPrestige">
<label class="btn-text">@L( "СБРОСИТЬ И ПОЛУЧИТЬ ПРЕСТИЖ", "PRESTIGE RESET" )</label>
</button>
</div>
}
<button class="menu-btn" onclick="@CloseWorkshopMenu">@L( "ЗАКРЫТЬ [R]", "CLOSE [R]" )</button>
</div>
</div>
}
@if ( IsMenuOpen )
{
<div class="main-menu-screen">
<div class="main-menu-sidebar">
<div class="brand-section">
<div class="logo-glow"></div>
<label class="brand-title">ONE MORE PUSH</label>
<label class="brand-subtitle">PHYSICS ARENA BRAWLER</label>
</div>
<div class="nav-buttons-group">
<button class="epic-btn play" onclick="@StartGame">
<div class="btn-glow"></div>
<label class="btn-icon">▶</label>
<div class="btn-content">
<label class="btn-title">@L( "В БОЙ", "PLAY NOW" )</label>
<label class="btn-sub">@L( "Выйти на арену", "Enter the Arena" )</label>
</div>
</button>
<button class="epic-btn" onclick="@OpenSettings">
<label class="btn-icon">⚙</label>
<div class="btn-content">
<label class="btn-title">@L( "НАСТРОЙКИ", "SETTINGS" )</label>
<label class="btn-sub">@L( "Параметры и управление", "Game options" )</label>
</div>
</button>
<button class="epic-btn quit" onclick="@QuitGame">
<label class="btn-icon">✖</label>
<div class="btn-content">
<label class="btn-title">@L( "ВЫХОД", "QUIT" )</label>
<label class="btn-sub">@L( "Покинуть игру", "Close Game" )</label>
</div>
</button>
</div>
<div class="player-profile-card">
<div class="profile-icon">🎮</div>
<div class="profile-info">
<label class="profile-name">@(Connection.Local?.DisplayName ?? L( "Боец", "Fighter" ))</label>
<label class="profile-stats">💰 @GetCredits() | 🏆 @GetHighScore()</label>
</div>
</div>
</div>
<div class="main-menu-leaderboard-panel">
<div class="lb-header">
<label class="lb-title">🏆 @L( "ТАБЛИЦА ЛИДЕРОВ", "GLOBAL LEADERBOARD" )</label>
<button class="lb-refresh-btn" onclick="@RefreshLeaderboard">🔄</button>
</div>
<div class="lb-tabs">
<button class="tab-btn @(CurrentLbStat == "pushes_count" ? "active" : "")" onclick="@(() => SelectLbStat("pushes_count"))">
🚀 @L( "ТОЛЧКИ", "PUSHES" )
</button>
<button class="tab-btn @(CurrentLbStat == "rounds_won" ? "active" : "")" onclick="@(() => SelectLbStat("rounds_won"))">
👑 @L( "ПОБЕДЫ", "WINS" )
</button>
</div>
<div class="lb-body">
@if ( IsLbLoading )
{
<div class="lb-status">@L( "Загрузка...", "LOADING..." )</div>
}
else if ( LbEntries == null || LbEntries.Count == 0 )
{
<div class="lb-status">@L( "Нет данных", "NO RECORD FOUND" )</div>
}
else
{
<div class="lb-table">
<div class="lb-table-header">
<span class="col-rank">#</span>
<span class="col-player">@L( "ИГРОК", "PLAYER" )</span>
<span class="col-score">@L( "СЧЕТ", "SCORE" )</span>
</div>
<div class="lb-table-rows">
@foreach ( var entry in LbEntries )
{
<div class="lb-row @(entry.SteamId == (Connection.Local?.SteamId ?? 0) ? "is-me" : "")">
<span class="col-rank">@GetRankBadge( entry.Rank )</span>
<span class="col-player">@entry.DisplayName</span>
<span class="col-score">@entry.Value.ToString("N0")</span>
</div>
}
</div>
</div>
}
</div>
</div>
<div class="main-menu-footer-badge">
<label class="footer-ver">v1.0.0 RELEASE</label>
<label class="footer-engine">POWERED BY SOURCE 2 & s&box</label>
</div>
</div>
}
@if ( IsSettingsOpen )
{
<div class="main-menu-screen">
<div class="main-menu-sidebar settings-wide">
<div class="brand-section">
<label class="brand-title">@L( "НАСТРОЙКИ", "SETTINGS" )</label>
<label class="brand-subtitle">CONFIGURATION PANEL</label>
</div>
<div class="settings-list-container">
<div class="setting-row-card">
<div class="setting-info">
<label class="title">🌐 @L( "Язык интерфейса", "Language" )</label>
<label class="desc">@L( "Выберите язык отображения", "Select UI Language" )</label>
</div>
<div class="lang-selector">
<button class="lang-btn @(CurrentLanguage == "EN" ? "active" : "")" onclick="@(() => SetLanguage("EN"))">EN</button>
<button class="lang-btn @(CurrentLanguage == "RU" ? "active" : "")" onclick="@(() => SetLanguage("RU"))">RU</button>
</div>
</div>
<div class="setting-row-card">
<div class="setting-info">
<label class="title">🤖 @L( "Спавн Ботов", "Bot Spawner" )</label>
<label class="desc">@L( "Автоматически добавлять ботов на карту", "Spawn AI bots in game" )</label>
</div>
<button class="lang-btn @(BotSpawner.EnableBots ? "active" : "")" onclick="@ToggleBots">
@(BotSpawner.EnableBots ? L( "ВКЛ", "ON" ) : L( "ВЫКЛ", "OFF" ))
</button>
</div>
<div class="setting-row-card">
<div class="setting-info">
<label class="title">🎵 @L( "Фоновая Музыка", "Music" )</label>
<label class="desc">@L( "Включить динамический саундтрек", "Toggle battle music" )</label>
</div>
<button class="lang-btn @(EnableMusic ? "active" : "")" onclick="@ToggleMusic">
@(EnableMusic ? L( "ВКЛ", "ON" ) : L( "ВЫКЛ", "OFF" ))
</button>
</div>
<div class="setting-row-card">
<div class="setting-info">
<label class="title">🎯 @L( "Чувствительность", "Sensitivity" )</label>
<label class="desc">@L( "Скорость поворота камеры", "Mouse sensitivity factor" )</label>
</div>
<label class="sens-badge">1.0</label>
</div>
</div>
<button class="epic-btn back" onclick="@CloseSettings">
<label class="btn-icon">⬅</label>
<div class="btn-content">
<label class="btn-title">@L( "НАЗАД", "BACK" )</label>
</div>
</button>
</div>
</div>
}
</root>
@code {
public class EntryItem
{
public int Rank { get; set; }
public string DisplayName { get; set; }
public double Value { get; set; }
public ulong SteamId { get; set; }
}
[Property] public PushAbility PushAbility { get; set; }
public PlayerHealth LocalHealth { get; private set; }
public bool IsMenuOpen { get; set; } = true;
public bool IsSettingsOpen { get; set; } = false;
public bool IsWorkshopMenuOpen { get; set; } = false;
public bool IsQSpawnMenuOpen { get; set; } = false;
public string ActiveTab { get; set; } = "PROPS";
public static string CurrentLanguage { get; set; } = "EN";
public bool IsLbLoading { get; set; } = true;
public string CurrentLbStat { get; set; } = "pushes_count";
public List<EntryItem> LbEntries { get; set; } = new();
public struct ChatMessage
{
public string Author { get; set; }
public string Text { get; set; }
}
public List<ChatMessage> CanvasMessages { get; set; } = new();
public bool IsChatOpen { get; set; } = false;
private TextEntry ChatInput { get; set; }
public struct PlayerInfo
{
public string Name { get; set; }
public int Score { get; set; }
public bool IsLocal { get; set; }
}
private string L( string ru, string en ) => CurrentLanguage == "RU" ? ru : en;
private bool IsReady => PushAbility?.IsReady ?? false;
private MetaProgression GetMeta() => Scene.GetAllComponents<MetaProgression>().FirstOrDefault();
private int GetCredits() => GetMeta()?.Credits ?? 0;
private int GetForceLevel() => GetMeta()?.ForceLevel ?? 0;
private int GetMassLevel() => GetMeta()?.MassLevel ?? 0;
private int GetEnergyLevel() => GetMeta()?.EnergyLevel ?? 0;
private int GetPrestigeLevel() => GetMeta()?.PrestigeLevel ?? 0;
private int GetPrestigeTokens() => GetMeta()?.PrestigeTokens ?? 0;
private bool CanPrestige() => GetMeta()?.CanPrestige() ?? false;
private void TryPrestige() => GetMeta()?.TryPrestige();
private int GetForceCost() => GetMeta()?.GetUpgradeCost(GetForceLevel()) ?? 0;
private int GetMassCost() => GetMeta()?.GetUpgradeCost(GetMassLevel()) ?? 0;
private int GetEnergyCost() => GetMeta()?.GetUpgradeCost(GetEnergyLevel()) ?? 0;
private void TryUpgradeForce() => GetMeta()?.TryUpgradeForce();
private void TryUpgradeMass() => GetMeta()?.TryUpgradeMass();
private void TryUpgradeEnergy() => GetMeta()?.TryUpgradeEnergy();
private float GetHealth() => LocalHealth?.CurrentHealth ?? 100f;
private float GetMaxHealth() => LocalHealth?.MaxHealth ?? 100f;
private float GetHealthPercent() => MathX.Clamp( (GetHealth() / GetMaxHealth()) * 100f, 0f, 100f );
private void SelectTab( string tab )
{
ActiveTab = tab;
StateHasChanged();
}
protected override void OnEnabled()
{
base.OnEnabled();
AddMessage( L( "Система", "System" ), L( "Чат готов. Нажмите Enter для ввода.", "Chat ready. Press Enter to type." ) );
ApplyMouseState();
_ = RefreshLeaderboard();
}
private async Task SelectLbStat( string statIdent )
{
if ( CurrentLbStat == statIdent ) return;
CurrentLbStat = statIdent;
await RefreshLeaderboard();
}
private async Task RefreshLeaderboard()
{
IsLbLoading = true;
StateHasChanged();
try
{
var board = Sandbox.Services.Leaderboards.GetFromStat( "seilordstudio.one_more_push", CurrentLbStat );
board.SetAggregationSum();
board.MaxEntries = 10;
await board.Refresh();
if ( board.Entries != null )
{
LbEntries = board.Entries.Select( x => new EntryItem
{
Rank = Convert.ToInt32( x.Rank ),
DisplayName = x.DisplayName ?? "Player",
Value = Convert.ToDouble( x.Value ),
SteamId = Convert.ToUInt64( x.SteamId )
} ).ToList();
}
else
{
LbEntries = new();
}
}
catch ( System.Exception ex )
{
Log.Warning( $"Leaderboard fetch error: {ex.Message}" );
}
IsLbLoading = false;
StateHasChanged();
}
private string GetRankBadge( int rank )
{
return rank switch
{
1 => "🥇 1",
2 => "🥈 2",
3 => "🥉 3",
_ => rank.ToString()
};
}
private bool IsEPressed()
{
return Input.Down( "use" ) || Input.Down( "e" ) || Input.Down( "E" );
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( PushAbility == null )
{
PushAbility = Scene.GetAllComponents<PushAbility>().FirstOrDefault( x => !x.IsProxy );
}
if ( LocalHealth == null )
{
LocalHealth = Scene.GetAllComponents<PlayerHealth>().FirstOrDefault( x => !x.IsProxy );
}
bool eDown = IsEPressed();
if ( eDown )
{
if ( !IsQSpawnMenuOpen && !IsChatOpen && !IsSettingsOpen && !IsMenuOpen )
{
IsWorkshopMenuOpen = false;
IsQSpawnMenuOpen = true;
ApplyMouseState();
StateHasChanged();
}
}
else
{
if ( IsQSpawnMenuOpen )
{
CloseQSpawnMenu();
}
}
if ( Input.EscapePressed || Input.Pressed( "menu" ) )
{
Input.EscapePressed = false;
ToggleMenu();
}
if ( Input.Pressed( "reload" ) || Input.Pressed( "r" ) || Input.Pressed( "R" ) )
{
if ( !IsMenuOpen && !IsSettingsOpen && !IsChatOpen && !IsQSpawnMenuOpen )
{
ToggleWorkshopMenu();
}
}
if ( Input.Pressed( "chat" ) || Input.Pressed( "enter" ) )
{
if ( !IsMenuOpen && !IsSettingsOpen && !IsWorkshopMenuOpen && !IsQSpawnMenuOpen )
{
if ( !IsChatOpen ) OpenChat();
}
}
ApplyMouseState();
}
private void SetLanguage( string lang )
{
CurrentLanguage = lang;
StateHasChanged();
}
private void ApplyMouseState()
{
bool needsMouse = IsMenuOpen || IsSettingsOpen || IsChatOpen || IsWorkshopMenuOpen || IsQSpawnMenuOpen;
Mouse.Visibility = needsMouse ? MouseVisibility.Visible : MouseVisibility.Hidden;
if ( !needsMouse && Panel != null )
{
Panel.Focus();
}
}
private void ToggleWorkshopMenu()
{
IsWorkshopMenuOpen = !IsWorkshopMenuOpen;
IsQSpawnMenuOpen = false;
ApplyMouseState();
StateHasChanged();
}
private void CloseWorkshopMenu()
{
IsWorkshopMenuOpen = false;
ApplyMouseState();
StateHasChanged();
}
private void CloseQSpawnMenu()
{
IsQSpawnMenuOpen = false;
ApplyMouseState();
StateHasChanged();
}
private void SelectAndSpawnProp( int index )
{
if ( PropSpawner.Instance != null )
{
PropSpawner.Instance.SpawnByIndex( index );
}
if ( IsWorkshopMenuOpen ) CloseWorkshopMenu();
}
private void ToggleMenu()
{
if ( IsSettingsOpen ) CloseSettings();
else if ( IsWorkshopMenuOpen ) CloseWorkshopMenu();
else if ( IsQSpawnMenuOpen ) CloseQSpawnMenu();
else if ( IsMenuOpen ) StartGame();
else OpenMenu();
}
private void StartGame()
{
IsMenuOpen = false;
IsSettingsOpen = false;
IsWorkshopMenuOpen = false;
IsQSpawnMenuOpen = false;
ApplyMouseState();
}
private void OpenMenu()
{
IsMenuOpen = true;
IsSettingsOpen = false;
IsWorkshopMenuOpen = false;
IsQSpawnMenuOpen = false;
ApplyMouseState();
_ = RefreshLeaderboard();
}
private void OpenSettings()
{
IsMenuOpen = false;
IsSettingsOpen = true;
IsWorkshopMenuOpen = false;
IsQSpawnMenuOpen = false;
ApplyMouseState();
}
private void CloseSettings()
{
IsSettingsOpen = false;
IsMenuOpen = true;
IsWorkshopMenuOpen = false;
IsQSpawnMenuOpen = false;
ApplyMouseState();
}
private void QuitGame() => Game.Close();
private int GetScore() => ScoreManager.Instance?.Score ?? 0;
private string GetFormattedTime()
{
var shrinker = Scene.GetAllComponents<ArenaShrinker>().FirstOrDefault();
if ( !shrinker.IsValid() ) return "02:00";
int minutes = (int)Math.Max( 0, shrinker.TimeRemaining ) / 60;
int seconds = (int)Math.Max( 0, shrinker.TimeRemaining ) % 60;
return $"{minutes:00}:{seconds:00}";
}
private float GetPercent()
{
if ( PushAbility == null ) return 0f;
return MathX.Clamp( PushAbility.CooldownProgress * 100f, 0f, 100f );
}
private void ToggleBots()
{
BotSpawner.EnableBots = !BotSpawner.EnableBots;
StateHasChanged();
}
private int GetHighScore() => SaveManager.Current?.HighScore ?? 0;
private void OpenChat()
{
IsChatOpen = true;
IsWorkshopMenuOpen = false;
IsQSpawnMenuOpen = false;
ApplyMouseState();
StateHasChanged();
ChatInput?.Focus();
}
private void SendChatMessage()
{
if ( ChatInput == null || string.IsNullOrWhiteSpace( ChatInput.Text ) )
{
IsChatOpen = false;
ApplyMouseState();
return;
}
string name = Connection.Local?.DisplayName ?? L( "Вы", "You" );
AddMessage( name, ChatInput.Text );
ChatInput.Text = "";
IsChatOpen = false;
ApplyMouseState();
}
public void AddMessage( string author, string text )
{
CanvasMessages.Add( new ChatMessage { Author = author, Text = text } );
if ( CanvasMessages.Count > 6 )
{
CanvasMessages.RemoveAt( 0 );
}
StateHasChanged();
}
private IEnumerable<PlayerInfo> GetPlayerList()
{
if ( Connection.All != null && Connection.All.Any() )
{
foreach ( var conn in Connection.All )
{
bool isLocal = conn == Connection.Local;
int score = isLocal ? GetScore() : 0;
yield return new PlayerInfo
{
Name = string.IsNullOrEmpty( conn.DisplayName ) ? "Player" : conn.DisplayName,
Score = score,
IsLocal = isLocal
};
}
}
else
{
yield return new PlayerInfo
{
Name = L( "Вы", "You" ),
Score = GetScore(),
IsLocal = true
};
}
}
private string GetWeatherTitle( WeatherType type ) => type switch
{
WeatherType.LowGravity => L( "НЕВЕСОМОСТЬ", "LOW GRAVITY" ),
WeatherType.SlipperyIce => L( "ГОЛОЛЁД", "SLIPPERY ICE" ),
WeatherType.StrongWind => L( "ШТОРМОВОЙ ВЕТЕР", "STRONG WIND" ),
_ => ""
};
private string GetWeatherIcon( WeatherType type ) => type switch
{
WeatherType.LowGravity => "🚀",
WeatherType.SlipperyIce => "❄️",
WeatherType.StrongWind => "🌪️",
_ => "⚠️"
};
private string GetWeatherClass( WeatherType type ) => type switch
{
WeatherType.LowGravity => "gravity",
WeatherType.SlipperyIce => "ice",
WeatherType.StrongWind => "wind",
_ => ""
};
private int GetDeaths() => ScoreManager.Instance?.Deaths ?? 0;
public static bool EnableMusic { get; set; } = true;
private void ToggleMusic()
{
EnableMusic = !EnableMusic;
MusicManager.Instance?.ToggleMusic( EnableMusic );
StateHasChanged();
}
protected override int BuildHash()
{
var hash = new HashCode();
hash.Add( GetPercent() );
hash.Add( GetHealthPercent() );
hash.Add( LocalHealth?.IsDead );
hash.Add( (int)(LocalHealth?.RespawnTimer * 10f ?? 0) );
hash.Add( IsReady );
hash.Add( MapEventManager.Instance?.CurrentEvent );
hash.Add( (int)(MapEventManager.Instance?.TimeRemaining * 10f ?? 0) );
hash.Add( EnableMusic );
hash.Add( PushAbility?.ChargeProgress );
hash.Add( PushAbility?.IsCharging );
hash.Add( GetScore() );
hash.Add( GetDeaths() );
hash.Add( GetFormattedTime() );
hash.Add( RoundManager.Instance?.CurrentRound );
hash.Add( IsMenuOpen );
hash.Add( IsWorkshopMenuOpen );
hash.Add( IsQSpawnMenuOpen );
hash.Add( ActiveTab );
hash.Add( GetCredits() );
hash.Add( GetForceLevel() );
hash.Add( GetMassLevel() );
hash.Add( GetEnergyLevel() );
hash.Add( GetPrestigeLevel() );
hash.Add( GetPrestigeTokens() );
hash.Add( CanPrestige() );
hash.Add( WeaponManager.Instance?.CurrentWeapon?.Id );
hash.Add( ShopManager.Instance?.SelectedItem?.Id );
hash.Add( IsSettingsOpen );
hash.Add( CurrentLanguage );
hash.Add( IsChatOpen );
hash.Add( CanvasMessages.Count );
hash.Add( Connection.All?.Count() ?? 0 );
hash.Add( Killfeed.Instance?.Entries.Count );
hash.Add( CurrentLbStat );
hash.Add( IsLbLoading );
hash.Add( LbEntries.Count );
return hash.ToHashCode();
}
}