s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
link Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=cherkbyte.one_more_push&take=20
Showing code results for query:
*
(51 total matches found)
Game
game
using Sandbox;
using System;
using System.Linq;
public sealed class BotAI : Component
{
[Property] public float Speed { get; set; } = 220f;
[Property] public float AttackRange { get; set; } = 220f;
private CharacterController _cc;
private PushAbility _push;
private ManeuverAbility _maneuver;
private TimeUntil _nextDecision;
private Vector3 _moveDir;
protected override void OnAwake()
{
_cc = Components.Get<CharacterController>();
_push = Components.Get<PushAbility>();
_maneuver = Components.Get<ManeuverAbility>();
}
protected override void OnUpdate()
{
if ( IsProxy ) return;
var target = FindTarget();
if ( target.IsValid() )
{
_moveDir = (target.WorldPosition - WorldPosition).WithZ( 0 ).Normal;
if ( _cc.IsValid() )
{
_cc.Velocity = _moveDir * Speed;
WorldRotation = Rotation.LookAt( _moveDir );
}
float dist = Vector3.DistanceBetween( WorldPosition, target.WorldPosition );
if ( dist <= AttackRange && _push.IsValid() && _push.IsReady )
{
_push.TryExecuteBotPush();
}
}
else
{
if ( _nextDecision )
{
_nextDecision = Game.Random.Float( 2f, 4f );
float angle = Game.Random.Float( 0f, MathF.PI * 2f );
_moveDir = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f );
}
if ( _cc.IsValid() )
{
_cc.Velocity = _moveDir * (Speed * 0.6f);
}
}
if ( _cc.IsValid() && !_cc.IsOnGround && _maneuver.IsValid() && _maneuver.IsDashReady )
{
_maneuver.TryExecuteBotDash( -_cc.Velocity.Normal );
}
}
private GameObject FindTarget()
{
return Scene.GetAllComponents<CharacterController>()
.Where( c => c.GameObject != GameObject && c.GameObject.IsValid() )
.OrderBy( c => Vector3.DistanceBetween( WorldPosition, c.WorldPosition ) )
.FirstOrDefault()?.GameObject;
}
}
Game
game
using Sandbox;
public sealed class BouncyShield : Component, Component.ITriggerListener
{
[Property] public float BounceForce { get; set; } = 1500f;
[Property] public SoundEvent BounceSound { get; set; }
public void OnTriggerEnter( Collider other )
{
var rootObj = other.GameObject.Root;
var pushDir = (rootObj.WorldPosition - WorldPosition).Normal;
pushDir.z = 0.3f;
bool bounced = false;
var cc = rootObj.Components.Get<CharacterController>();
if ( cc.IsValid() )
{
cc.Velocity = pushDir * BounceForce;
bounced = true;
}
var rb = rootObj.Components.Get<Rigidbody>();
if ( rb.IsValid() )
{
rb.Velocity = pushDir * BounceForce;
bounced = true;
}
if ( bounced )
{
if ( BounceSound != null )
{
Sound.Play( BounceSound, WorldPosition );
}
CameraJuice.Instance?.Shake( 12f );
}
}
}
Game
game
using Sandbox;
public sealed class EmoteWheel : Component
{
[Property] public string CurrentEmote { get; private set; } = "";
[Property] public float EmoteDuration { get; set; } = 2.5f;
private TimeSince _emoteTimer;
public void PlayEmote( string emoteIcon )
{
CurrentEmote = emoteIcon;
_emoteTimer = 0;
}
protected override void OnUpdate()
{
if ( !string.IsNullOrEmpty( CurrentEmote ) && _emoteTimer > EmoteDuration )
{
CurrentEmote = "";
}
}
}
Game
game
using Sandbox;
using System;
using System.Linq;
public sealed class PlayerHealth : Component
{
[Property] public float MaxHealth { get; set; } = 100f;
[Property] public float CurrentHealth { get; private set; } = 100f;
public bool IsDead => CurrentHealth <= 0f;
public float RespawnTimer { get; private set; } = 0f;
public float RespawnDuration { get; set; } = 3f;
private Vector3 _lastDamageDirection = Vector3.Zero;
protected override void OnStart()
{
CurrentHealth = MaxHealth;
}
protected override void OnUpdate()
{
if ( IsDead )
{
RespawnTimer -= Time.Delta;
if ( RespawnTimer <= 0f )
{
Respawn();
}
}
}
public void TakeDamage( float damage )
{
TakeDamage( damage, Vector3.Zero );
}
public void TakeDamage( float damage, Vector3 direction )
{
if ( IsDead ) return;
_lastDamageDirection = direction;
CurrentHealth = MathX.Clamp( CurrentHealth - damage, 0f, MaxHealth );
if ( CurrentHealth <= 0f )
{
Die();
}
}
public void Heal( float amount )
{
if ( IsDead ) return;
CurrentHealth = MathX.Clamp( CurrentHealth + amount, 0f, MaxHealth );
}
private void Die()
{
RespawnTimer = RespawnDuration;
if ( ScoreManager.Instance != null )
{
ScoreManager.Instance.AddDeath();
}
var ragdoll = Components.Get<PlayerRagdoll>();
if ( ragdoll.IsValid() )
{
ragdoll.SpawnRagdoll( _lastDamageDirection * 2500f );
}
var skm = Components.Get<SkinnedModelRenderer>();
if ( skm.IsValid() ) skm.Enabled = false;
var mr = Components.Get<ModelRenderer>();
if ( mr.IsValid() ) mr.Enabled = false;
var cc = Components.Get<CharacterController>();
if ( cc.IsValid() ) cc.Enabled = false;
var rb = Components.Get<Rigidbody>();
if ( rb.IsValid() ) rb.Enabled = false;
CameraJuice.Instance?.Shake( 30f, 6f );
Log.Info( "[PlayerHealth] Игрок погиб!" );
}
public void Respawn()
{
CurrentHealth = MaxHealth;
var skm = Components.Get<SkinnedModelRenderer>();
if ( skm.IsValid() ) skm.Enabled = true;
var mr = Components.Get<ModelRenderer>();
if ( mr.IsValid() ) mr.Enabled = true;
var cc = Components.Get<CharacterController>();
if ( cc.IsValid() ) cc.Enabled = true;
var rb = Components.Get<Rigidbody>();
if ( rb.IsValid() ) rb.Enabled = true;
var spawnPoint = Scene.GetAllComponents<SpawnPoint>().FirstOrDefault();
if ( spawnPoint.IsValid() )
{
Transform.World = spawnPoint.Transform.World;
}
else
{
Transform.LocalPosition = Vector3.Up * 50f;
}
Log.Info( "[PlayerHealth] Игрок возродился!" );
}
}
Game
game
@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( "В МЕНЮ", "MAIN MENU" )</label>
<label class="btn-sub">@L( "Вернуться в главное меню", "Return to Main Menu" )</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.ActiveScene.LoadFromFile( "scenes/mainmenu.scene" );
}
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();
}
}
Game
game
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "One More Push" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "one_more_push" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "cherkbyte" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "cherkbyte.one_more_push" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "29" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]
[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-10-06T04:15:32.2273688Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.110.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.110.0")]
Game
game
using Sandbox;
using System.Linq;
public enum ArenaRoundState
{
WaitingForPlayers,
Countdown,
InGame,
RoundEnd
}
public sealed class ArenaRoundManager : Component
{
[Property] public float CountdownDuration { get; set; } = 5.0f;
[Property] public float RoundDuration { get; set; } = 60.0f;
[Property] public float EndPauseDuration { get; set; } = 4.0f;
public ArenaRoundState CurrentState { get; private set; } = ArenaRoundState.WaitingForPlayers;
public TimeUntil StateTimer { get; private set; }
protected override void OnStart()
{
SetState( ArenaRoundState.WaitingForPlayers );
}
protected override void OnUpdate()
{
switch ( CurrentState )
{
case ArenaRoundState.WaitingForPlayers:
if ( Connection.All.Count >= 1 )
{
SetState( ArenaRoundState.Countdown );
}
break;
case ArenaRoundState.Countdown:
if ( StateTimer )
{
SetState( ArenaRoundState.InGame );
}
break;
case ArenaRoundState.InGame:
if ( StateTimer )
{
SetState( ArenaRoundState.RoundEnd );
}
break;
case ArenaRoundState.RoundEnd:
if ( StateTimer )
{
SetState( ArenaRoundState.WaitingForPlayers );
}
break;
}
}
private void SetState( ArenaRoundState newState )
{
CurrentState = newState;
switch ( newState )
{
case ArenaRoundState.WaitingForPlayers:
Log.Info( "[ArenaRoundManager] Ожидание игроков..." );
break;
case ArenaRoundState.Countdown:
StateTimer = CountdownDuration;
Log.Info( $"[ArenaRoundManager] Отсчёт! До старта: {CountdownDuration} сек." );
break;
case ArenaRoundState.InGame:
StateTimer = RoundDuration;
Log.Info( "[ArenaRoundManager] РАУНД НАЧАЛСЯ!" );
break;
case ArenaRoundState.RoundEnd:
StateTimer = EndPauseDuration;
Log.Info( "[ArenaRoundManager] Раунд окончен!" );
break;
}
}
}
Game
game
using Sandbox;
using System;
using System.Linq;
public sealed class ExplosiveBarrel : Component, Component.ICollisionListener
{
[Property] public float ExplosionRadius { get; set; } = 450f;
[Property] public float ExplosionForce { get; set; } = 65000f;
[Property] public float ImpactThreshold { get; set; } = 250f;
[Property] public GameObject ExplosionParticle { get; set; }
[Property] public SoundEvent ExplosionSound { get; set; }
private bool _hasExploded = false;
public void Explode()
{
if ( _hasExploded || !GameObject.IsValid() ) return;
_hasExploded = true;
if ( ExplosionParticle.IsValid() )
{
ExplosionParticle.Clone( WorldPosition );
}
if ( ExplosionSound != null )
{
Sound.Play( ExplosionSound, WorldPosition );
}
var hits = Scene.Trace.Sphere( ExplosionRadius, WorldPosition, WorldPosition + Vector3.Up * 2f )
.IgnoreGameObject( GameObject )
.RunAll();
foreach ( var hit in hits )
{
if ( !hit.GameObject.IsValid() ) continue;
var targetObj = hit.GameObject.Root;
var shield = targetObj.Components.Get<ShieldAbility>( true );
if ( shield.IsValid() && shield.IsShieldActive ) continue;
var chainBarrel = targetObj.Components.Get<ExplosiveBarrel>();
if ( chainBarrel.IsValid() && chainBarrel != this )
{
chainBarrel.Explode();
continue;
}
var rb = targetObj.Components.Get<Rigidbody>();
if ( rb.IsValid() )
{
var dir = (hit.GameObject.WorldPosition - WorldPosition).Normal;
dir.z = MathF.Max( dir.z, 0.4f );
rb.ApplyImpulse( dir * ExplosionForce );
}
}
Log.Info( $"[ExplosiveBarrel] Booom! Barrel exploded at {WorldPosition}" );
GameObject.Destroy();
}
public void OnCollisionStart( Collision collision )
{
if ( collision.Contact.Speed.Length > ImpactThreshold )
{
Explode();
}
}
}
Game
game
using Sandbox;
public sealed class HeavyAnvil : Component, Component.ICollisionListener
{
[Property] public float PushResistanceMultiplier { get; set; } = 0.25f;
[Property] public float CrushSpeedThreshold { get; set; } = 300f;
[Property] public float CrushImpactForce { get; set; } = 75000f;
[Property] public SoundEvent ImpactSound { get; set; }
public void OnCollisionStart( Collision collision )
{
if ( collision.Contact.Speed.Length < CrushSpeedThreshold ) return;
var targetObj = collision.Other.GameObject?.Root;
if ( !targetObj.IsValid() || targetObj == GameObject.Root ) return;
if ( ImpactSound != null )
{
Sound.Play( ImpactSound, WorldPosition );
}
var cc = targetObj.Components.Get<CharacterController>();
if ( cc.IsValid() )
{
var dir = (targetObj.WorldPosition - WorldPosition).Normal;
dir.z = 0.5f;
cc.Velocity += dir * 900f;
}
var rb = targetObj.Components.Get<Rigidbody>();
if ( rb.IsValid() )
{
var dir = (targetObj.WorldPosition - WorldPosition).Normal;
dir.z = 0.5f;
rb.ApplyImpulse( dir * CrushImpactForce );
}
}
}
Game
game
using Sandbox;
using System.Collections.Generic;
public sealed class Killfeed : Component
{
public static Killfeed Instance { get; private set; }
public struct KillEntry
{
public string KillerName { get; set; }
public string VictimName { get; set; }
public string Method { get; set; }
public float Time { get; set; }
}
public List<KillEntry> Entries { get; private set; } = new();
protected override void OnStart()
{
Instance = this;
}
public void AddEntry( string killer, string victim, string method = "Push" )
{
Entries.Add( new KillEntry
{
KillerName = killer,
VictimName = victim,
Method = method,
Time = RealTime.Now
} );
if ( Entries.Count > 5 )
{
Entries.RemoveAt( 0 );
}
}
}
Game
game
using Sandbox;
public sealed class ObjectGrabber : Component
{
[Property] public float MaxReachDistance { get; set; } = 300f;
[Property] public float HoldDistance { get; set; } = 150f;
[Property] public float ThrowForce { get; set; } = 1200f;
private GameObject _heldObject;
private Rigidbody _heldRigidbody;
protected override void OnUpdate()
{
if ( IsProxy ) return;
if ( Input.Pressed( "use" ) || Input.Pressed( "e" ) )
{
if ( _heldObject.IsValid() )
{
DropObject();
}
else
{
TryGrabObject();
}
}
if ( Input.Pressed( "attack1" ) && _heldObject.IsValid() )
{
ThrowObject();
}
if ( _heldObject.IsValid() )
{
UpdateHeldObjectPosition();
}
}
private void TryGrabObject()
{
var cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
if ( !cam.IsValid() ) return;
var ray = cam.Transform.World.Forward;
var start = cam.WorldPosition;
var end = start + ray * MaxReachDistance;
var tr = Scene.Trace.Ray( start, end )
.IgnoreGameObject( GameObject )
.Run();
if ( tr.Hit && tr.GameObject.IsValid() )
{
var rootObj = tr.GameObject.Root;
var rb = rootObj.Components.Get<Rigidbody>() ?? tr.GameObject.Components.Get<Rigidbody>();
if ( rb.IsValid() && !rootObj.Tags.Has( "player" ) )
{
_heldObject = rootObj;
_heldRigidbody = rb;
_heldRigidbody.MotionEnabled = true;
}
}
}
private void UpdateHeldObjectPosition()
{
var cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
if ( !cam.IsValid() ) return;
var targetPos = cam.WorldPosition + cam.Transform.World.Forward * HoldDistance;
var dir = targetPos - _heldObject.WorldPosition;
if ( _heldRigidbody.IsValid() )
{
_heldRigidbody.Velocity = dir * 10f;
}
else
{
_heldObject.WorldPosition = Vector3.Lerp( _heldObject.WorldPosition, targetPos, Time.Delta * 15f );
}
}
private void DropObject()
{
_heldObject = null;
_heldRigidbody = null;
}
private void ThrowObject()
{
if ( !_heldObject.IsValid() ) return;
var cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
var throwDir = cam.IsValid() ? cam.Transform.World.Forward : Transform.World.Forward;
if ( _heldRigidbody.IsValid() )
{
_heldRigidbody.Velocity = throwDir * ThrowForce;
}
CameraJuice.Instance?.Shake( 8f );
_heldObject = null;
_heldRigidbody = null;
}
}
Game
game
global using Sandbox; global using System.Collections.Generic; global using System.Linq;
Game
game
using Sandbox;
using System;
using System.Linq;
public enum WeatherType
{
None,
LowGravity,
SlipperyIce,
StrongWind
}
public sealed class MapEventManager : Component
{
public static MapEventManager Instance { get; private set; }
[Property] public float EventDuration { get; set; } = 12f;
[Property] public float EventInterval { get; set; } = 25f;
[Property] public float LowGravityForce { get; set; } = 450f;
[Property] public float WindForce { get; set; } = 400f;
public WeatherType CurrentEvent { get; private set; } = WeatherType.None;
public Vector3 CurrentWindDirection { get; private set; } = Vector3.Right;
public float TimeRemaining { get; private set; }
private TimeUntil _nextEventTime;
private TimeUntil _eventEndTime;
protected override void OnAwake()
{
Instance = this;
_nextEventTime = EventInterval;
}
protected override void OnUpdate()
{
if ( IsProxy ) return;
if ( CurrentEvent == WeatherType.None && _nextEventTime )
{
StartRandomEvent();
}
else if ( CurrentEvent != WeatherType.None )
{
TimeRemaining = _eventEndTime;
if ( _eventEndTime )
{
EndEvent();
}
}
}
protected override void OnFixedUpdate()
{
if ( CurrentEvent == WeatherType.None ) return;
var players = Scene.GetAllComponents<CharacterController>();
var rigidbodies = Scene.GetAllComponents<Rigidbody>();
if ( CurrentEvent == WeatherType.LowGravity )
{
foreach ( var cc in players )
{
if ( cc.IsValid() )
{
cc.Velocity += Vector3.Up * LowGravityForce * Time.Delta;
}
}
foreach ( var rb in rigidbodies )
{
if ( rb.IsValid() && !rb.IsProxy )
{
rb.ApplyImpulse( Vector3.Up * (rb.PhysicsBody.Mass * LowGravityForce * Time.Delta) );
}
}
}
else if ( CurrentEvent == WeatherType.StrongWind )
{
foreach ( var cc in players )
{
if ( cc.IsValid() )
{
cc.Velocity += CurrentWindDirection * WindForce * Time.Delta;
}
}
foreach ( var rb in rigidbodies )
{
if ( rb.IsValid() && !rb.IsProxy )
{
rb.ApplyImpulse( CurrentWindDirection * WindForce * 15f * Time.Delta );
}
}
}
else if ( CurrentEvent == WeatherType.SlipperyIce )
{
foreach ( var cc in players )
{
if ( cc.IsValid() && cc.IsOnGround )
{
var vel = cc.Velocity;
vel.x *= 1.015f;
vel.y *= 1.015f;
cc.Velocity = vel;
}
}
}
}
private void StartRandomEvent()
{
var values = Enum.GetValues( typeof( WeatherType ) ).Cast<WeatherType>().Where( w => w != WeatherType.None ).ToArray();
CurrentEvent = values[Random.Shared.Next( values.Length )];
_eventEndTime = EventDuration;
if ( CurrentEvent == WeatherType.StrongWind )
{
float angle = Random.Shared.NextSingle() * MathF.PI * 2f;
CurrentWindDirection = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f ).Normal;
}
Log.Info( $"[MapEventManager] Запущен погодный ивент: {CurrentEvent}" );
}
private void EndEvent()
{
Log.Info( $"[MapEventManager] Завершён ивент: {CurrentEvent}" );
CurrentEvent = WeatherType.None;
_nextEventTime = EventInterval;
}
}
Game
game
using Sandbox;
using System;
public sealed class PlayerAbilities : Component
{
[Property] public GameObject HeavyObjectPrefab { get; set; }
[Property] public float DropCooldown { get; set; } = 8f;
[Property] public float ShieldCooldown { get; set; } = 6f;
[Property] public float ShieldDuration { get; set; } = 2f;
public bool IsShieldActive { get; private set; } = false;
private TimeSince _timeSinceDrop = 100f;
private TimeSince _timeSinceShield = 100f;
private TimeSince _shieldActiveTimer = 0f;
private Rigidbody _rb;
private float _originalMass = 0f;
protected override void OnStart()
{
_rb = Components.Get<Rigidbody>();
if ( _rb.IsValid() )
{
_originalMass = _rb.MassOverride;
}
}
protected override void OnUpdate()
{
if ( IsProxy ) return;
if ( IsShieldActive && _shieldActiveTimer > ShieldDuration )
{
DeactivateShield();
}
if ( Input.Pressed( "attack2" ) || Input.Pressed( "use" ) )
{
TryDropObject();
}
if ( Input.Pressed( "run" ) )
{
TryActivateShield();
}
}
private void TryDropObject()
{
if ( _timeSinceDrop < DropCooldown )
{
Log.Info( $"[Способности] Сброс на перезарядке! Осталось: {DropCooldown - _timeSinceDrop:F1} сек." );
return;
}
var headPos = WorldPosition + Vector3.Up * 50f;
var forwardPos = headPos + WorldTransform.Forward * 600f;
var tr = Scene.Trace.Ray( headPos, forwardPos )
.IgnoreGameObjectHierarchy( GameObject )
.Run();
if ( tr.Hit && tr.GameObject.IsValid() )
{
var targetPos = tr.GameObject.WorldPosition;
var spawnPos = targetPos + Vector3.Up * 180f;
if ( HeavyObjectPrefab.IsValid() )
{
var obj = HeavyObjectPrefab.Clone( spawnPos );
if ( obj.IsValid() && Network.Active )
{
obj.NetworkSpawn();
}
_timeSinceDrop = 0;
Log.Info( "[Способности] Тяжелый объект сброшен прямо на врага!" );
}
else
{
Log.Warning( "[Способности] HeavyObjectPrefab не назначен в Инспекторе!" );
}
}
else
{
Log.Info( "[Способности] Никого не нашли перед собой для атаки!" );
}
}
private void TryActivateShield()
{
if ( _timeSinceShield < ShieldCooldown || IsShieldActive ) return;
IsShieldActive = true;
_shieldActiveTimer = 0;
_timeSinceShield = 0;
if ( _rb.IsValid() )
{
_originalMass = _rb.MassOverride;
_rb.MassOverride = 1000f;
}
Log.Info( "[Способности] ЩИТ АКТИВИРОВАН!" );
}
private void DeactivateShield()
{
IsShieldActive = false;
if ( _rb.IsValid() )
{
_rb.MassOverride = _originalMass;
}
Log.Info( "[Способности] Действие щита закончилось." );
}
}
Game
game
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Services;
@using System;
@using System.Threading.Tasks;
@using System.Collections.Generic;
@using System.Linq;
@inherits Panel
<root class="lb-root @(IsOpen ? "open" : "")">
<div class="lb-card">
<div class="lb-header">
<div class="lb-title">GLOBAL LEADERBOARD</div>
<button class="lb-close" onclick="@Toggle">✕</button>
</div>
<div class="lb-tabs">
<button class="tab-btn @(CurrentStat == "pushes_count" ? "active" : "")" onclick="@(() => SelectStat("pushes_count"))">
PUSHES
</button>
<button class="tab-btn @(CurrentStat == "rounds_won" ? "active" : "")" onclick="@(() => SelectStat("rounds_won"))">
WINS
</button>
</div>
<div class="lb-content">
@if (IsLoading)
{
<div class="lb-status">LOADING DATA...</div>
}
else if (Entries == null || Entries.Count == 0)
{
<div class="lb-status">NO RECORD FOUND</div>
}
else
{
<div class="lb-table">
<div class="table-header">
<span class="col-rank">#</span>
<span class="col-player">PLAYER</span>
<span class="col-score">SCORE</span>
</div>
<div class="table-body">
@foreach (var entry in Entries)
{
<div class="table-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>
</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; }
}
public bool IsOpen { get; set; } = false;
public bool IsLoading { get; set; } = true;
public string CurrentStat { get; set; } = "pushes_count";
public List<EntryItem> Entries { get; set; } = new();
protected override void OnAfterTreeRender( bool firstTime )
{
base.OnAfterTreeRender( firstTime );
if ( firstTime )
{
_ = RefreshData();
}
}
public void Toggle()
{
IsOpen = !IsOpen;
if (IsOpen)
{
_ = RefreshData();
}
StateHasChanged();
}
private async Task SelectStat(string statIdent)
{
if (CurrentStat == statIdent) return;
CurrentStat = statIdent;
await RefreshData();
}
private async Task RefreshData()
{
IsLoading = true;
StateHasChanged();
try
{
var board = Leaderboards.GetFromStat("seilordstudio.one_more_push", CurrentStat);
board.SetAggregationSum();
board.MaxEntries = 20;
await board.Refresh();
if (board.Entries != null)
{
Entries = 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
{
Entries = new();
}
}
catch (System.Exception ex)
{
Log.Warning($"Leaderboard fetch failed: {ex.Message}");
}
IsLoading = false;
StateHasChanged();
}
private string GetRankBadge(int rank)
{
return rank switch
{
1 => "🥇 1",
2 => "🥈 2",
3 => "🥉 3",
_ => rank.ToString()
};
}
}
Game
game
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public sealed class RoundManager : Component
{
public static RoundManager Instance { get; private set; }
[Property] public float RoundTime { get; set; } = 120f;
[Sync] public float TimeLeft { get; set; }
[Sync] public bool IsInRound { get; set; }
[Sync] public int CurrentRound { get; set; } = 1;
protected override void OnAwake()
{
Instance = this;
}
protected override void OnStart()
{
StartNewRound();
}
public void StartNewRound()
{
TimeLeft = RoundTime;
IsInRound = true;
ResetArena();
RespawnAllPlayers();
var shrinker = Scene.GetAllComponents<ArenaShrinker>().FirstOrDefault();
if ( shrinker.IsValid() )
{
shrinker.StartRound();
}
Log.Info( $"[RoundManager] Раунд {CurrentRound} начат!" );
}
public void OnPlayerEliminated( GameObject player )
{
if ( !IsInRound ) return;
if ( player.IsValid() )
{
player.Enabled = false;
}
var activePlayers = Scene.GetAllComponents<PushAbility>()
.Where( p => p.GameObject.IsValid() && p.GameObject.Enabled );
if ( activePlayers.Count() <= 1 )
{
_ = EndRound();
}
}
public async Task EndRound()
{
if ( !IsInRound ) return;
var winner = Scene.GetAllComponents<PushAbility>()
.FirstOrDefault( p => p.GameObject.IsValid() && p.GameObject.Enabled );
if ( winner.IsValid() && !winner.IsProxy )
{
GameServices.RecordRoundWin();
}
_ = GameServices.SaveStatsAsync();
IsInRound = false;
CurrentRound++;
Log.Info( $"Раунд {CurrentRound - 1} завершен! Перезапуск через 3 секунды..." );
await Task.DelaySeconds( 3f );
StartNewRound();
}
private void ResetArena()
{
var tiles = Scene.GetAllComponents<ArenaTile>();
foreach ( var tile in tiles )
{
if ( tile.IsValid() )
{
tile.ResetTile();
}
}
}
private void RespawnAllPlayers()
{
var spawn = Scene.GetAllComponents<SpawnPoint>().FirstOrDefault();
Vector3 spawnPos = spawn.IsValid() ? spawn.WorldPosition : new Vector3( 0, 0, 250 );
var players = Scene.GetAllComponents<PushAbility>();
foreach ( var player in players )
{
if ( !player.IsValid() ) continue;
var root = player.GameObject.Root;
root.Enabled = true;
root.WorldPosition = spawnPos;
var cc = root.Components.Get<CharacterController>( true );
if ( cc.IsValid() )
{
cc.Velocity = Vector3.Zero;
}
var rb = root.Components.Get<Rigidbody>( true );
if ( rb.IsValid() )
{
rb.Velocity = Vector3.Zero;
rb.AngularVelocity = Vector3.Zero;
}
}
}
}
Game
game
using Sandbox;
using System.Collections.Generic;
using System.Linq;
public class PushItem
{
public string Id { get; set; }
public string NameRu { get; set; }
public string NameEn { get; set; }
public int Cost { get; set; }
public float ForceMultiplier { get; set; }
public string Key { get; set; }
public bool IsUnlocked { get; set; }
public string GetName( string lang ) => lang == "EN" ? NameEn : NameRu;
}
public class ShopManager : Component
{
public static ShopManager Instance { get; private set; }
public List<PushItem> Items { get; private set; } = new()
{
new PushItem { Id = "basic", NameRu = "Обычный", NameEn = "Basic", Cost = 0, ForceMultiplier = 1.0f, Key = "1", IsUnlocked = true },
new PushItem { Id = "heavy", NameRu = "Тяжелый", NameEn = "Heavy", Cost = 10, ForceMultiplier = 1.5f, Key = "2", IsUnlocked = false },
new PushItem { Id = "force", NameRu = "Импульс", NameEn = "Impulse", Cost = 25, ForceMultiplier = 2.2f, Key = "3", IsUnlocked = false },
new PushItem { Id = "mega", NameRu = "Мега-толчок", NameEn = "Mega Push", Cost = 50, ForceMultiplier = 3.5f, Key = "4", IsUnlocked = false }
};
public PushItem SelectedItem { get; private set; }
protected override void OnStart()
{
Instance = this;
SelectedItem = Items.FirstOrDefault( x => x.IsUnlocked );
}
protected override void OnUpdate()
{
if ( Input.Pressed( "slot1" ) ) SelectByKey( "1" );
if ( Input.Pressed( "slot2" ) ) SelectByKey( "2" );
if ( Input.Pressed( "slot3" ) ) SelectByKey( "3" );
if ( Input.Pressed( "slot4" ) ) SelectByKey( "4" );
}
public void SelectByKey( string key )
{
var item = Items.FirstOrDefault( x => x.Key == key );
if ( item != null ) TryBuyOrSelect( item );
}
public bool TryBuyOrSelect( PushItem item )
{
if ( item.IsUnlocked )
{
SelectedItem = item;
return true;
}
int currentScore = ScoreManager.Instance?.Score ?? 0;
if ( currentScore >= item.Cost )
{
item.IsUnlocked = true;
SelectedItem = item;
return true;
}
return false;
}
public float GetCurrentForceMultiplier()
{
return SelectedItem?.ForceMultiplier ?? 1.0f;
}
}
Game
game
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
<root>
<div class="background-image"></div>
<div class="background-overlay"></div>
<div class="header">
<div class="logo">
<span class="logo-text">@GetText("game_title")</span>
</div>
<div class="header-actions">
<button class="icon-button" onclick="@OnSettingsClicked" onmouseover="@PlayHoverSound">
<span>⚙️️ @GetText("settings_btn")</span>
</button>
<button class="icon-button lang" onclick="@OnLangClicked" onmouseover="@PlayHoverSound">
<span>🌐 @GetText("lang_btn")</span>
</button>
<button class="icon-button exit-btn" onclick="@OnQuitClicked" onmouseover="@PlayHoverSound">
<span>🚪 @GetText("exit_btn")</span>
</button>
</div>
</div>
<div class="main-content">
<div class="modes-section">
<div class="title-group">
<div class="main-title">@GetText("title")</div>
<div class="subtitle">@GetText("subtitle")</div>
</div>
<div class="modes-wrapper">
<div class="mode-card sumo" onclick="@(() => OnModeClicked(GameMode.SumoPush))" onmouseover="@PlayHoverSound">
<div class="card-badge sumo-badge">@GetText("sumo_tag")</div>
<div class="card-icon-wrapper">
<span class="card-icon">🤼</span>
</div>
<div class="card-content">
<div class="mode-title">@GetText("sumo_title")</div>
<div class="mode-desc">@GetText("sumo_desc")</div>
</div>
</div>
<div class="mode-card nextbot disabled">
<div class="card-badge nextbot-badge disabled-badge">@GetText("nextbot_tag")</div>
<div class="card-icon-wrapper">
<span class="card-icon">🏃</span>
</div>
<div class="card-content">
<div class="mode-title">@GetText("nextbot_title")</div>
<div class="mode-desc">@GetText("nextbot_desc")</div>
</div>
</div>
</div>
</div>
<div class="news-panel">
<div class="news-header">
<span class="news-title">@GetText("news_title")</span>
<span class="news-badge">@GetText("news_tag_new")</span>
</div>
<div class="news-list">
<div class="news-item item-highlight" onmouseover="@PlayHoverSound">
<div class="news-item-title">🔥 @GetText("news_item1_title")</div>
<div class="news-item-desc">@GetText("news_item1_desc")</div>
</div>
<div class="news-item" onmouseover="@PlayHoverSound">
<div class="news-item-title">✨ @GetText("news_item2_title")</div>
<div class="news-item-desc">@GetText("news_item2_desc")</div>
</div>
<div class="news-item" onmouseover="@PlayHoverSound">
<div class="news-item-title">⚡ @GetText("news_item3_title")</div>
<div class="news-item-desc">@GetText("news_item3_desc")</div>
</div>
</div>
</div>
</div>
@if ( IsSettingsOpen )
{
<div class="modal-overlay">
<div class="modal-window">
<div class="modal-header">
<div class="modal-title">@GetText("settings_title")</div>
<button class="close-x" onclick="@OnCloseSettingsClicked" onmouseover="@PlayHoverSound">✕</button>
</div>
<div class="modal-body">
<div class="setting-item">
<div class="setting-label">@GetText("volume_label"): @((int)(Volume * 100))%</div>
<div class="volume-buttons">
<button class="vol-btn" onclick="@(() => OnVolumeBtnClicked(0.25f))" onmouseover="@PlayHoverSound">25%</button>
<button class="vol-btn" onclick="@(() => OnVolumeBtnClicked(0.50f))" onmouseover="@PlayHoverSound">50%</button>
<button class="vol-btn" onclick="@(() => OnVolumeBtnClicked(0.75f))" onmouseover="@PlayHoverSound">75%</button>
<button class="vol-btn" onclick="@(() => OnVolumeBtnClicked(1.00f))" onmouseover="@PlayHoverSound">100%</button>
</div>
</div>
<div class="controls-section">
<div class="controls-title">@GetText("controls_title")</div>
<div class="control-row">@GetText("ctrl_move")</div>
<div class="control-row">@GetText("ctrl_action")</div>
<div class="control-row">@GetText("ctrl_space")</div>
</div>
</div>
<div class="modal-footer">
<button class="modal-close-btn" onclick="@OnCloseSettingsClicked" onmouseover="@PlayHoverSound">@GetText("close_btn")</button>
</div>
</div>
</div>
}
@if ( IsLoading )
{
<div class="loading-overlay">
<div class="loading-box">
<div class="loading-mode-title">@(TargetMode == GameMode.SumoPush ? GetText("sumo_title") : GetText("nextbot_title"))</div>
<div class="loading-status">@GetText("loading_title")</div>
<div class="loading-sub">@GetText("loading_sub")</div>
<div class="progress-bar-bg">
<div class="progress-bar-fill" style="width: @(LoadingProgress)%;"></div>
</div>
<div class="loading-percent">@((int)LoadingProgress)%</div>
<div class="tip-box">
<span class="tip-label">@GetText("tip_label")</span>
<span class="tip-text">@(TargetMode == GameMode.SumoPush ? GetText("tip_sumo") : GetText("tip_nextbot"))</span>
</div>
</div>
</div>
}
</root>
@code {
[Property] public SoundEvent HoverSound { get; set; }
[Property] public SoundEvent ClickSound { get; set; }
private bool IsSettingsOpen { get; set; } = false;
private bool IsLoading { get; set; } = false;
private GameMode TargetMode { get; set; }
private float LoadingProgress { get; set; } = 0f;
private TimeSince TimeSinceLoadingStarted;
private float Volume { get; set; } = 1.0f;
private const float TotalLoadingTime = 6.0f;
private void PlayHoverSound()
{
if ( HoverSound != null )
Sound.Play( HoverSound );
else
Sound.Play( "ui.button.hover" );
}
private void PlayClickSound()
{
if ( ClickSound != null )
Sound.Play( ClickSound );
else
Sound.Play( "ui.button.press" );
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( IsLoading )
{
float elapsed = TimeSinceLoadingStarted;
LoadingProgress = MathX.Clamp( (elapsed / TotalLoadingTime) * 100f, 0f, 100f );
if ( elapsed >= TotalLoadingTime )
{
IsLoading = false;
if ( ModeController.Instance != null )
{
ModeController.Instance.SelectMode( TargetMode );
}
}
}
}
private void OnModeClicked( GameMode mode )
{
PlayClickSound();
StartLoading( mode );
}
private void OnSettingsClicked()
{
PlayClickSound();
ToggleSettings();
}
private void OnCloseSettingsClicked()
{
PlayClickSound();
ToggleSettings();
}
private void OnLangClicked()
{
PlayClickSound();
OnToggleLanguage();
}
private void OnQuitClicked()
{
PlayClickSound();
Game.Close();
}
private void OnVolumeBtnClicked( float val )
{
PlayClickSound();
SetVolume( val );
}
private void StartLoading( GameMode mode )
{
if ( IsLoading ) return;
TargetMode = mode;
IsLoading = true;
LoadingProgress = 0f;
TimeSinceLoadingStarted = 0;
}
private void ToggleSettings()
{
if ( IsLoading ) return;
IsSettingsOpen = !IsSettingsOpen;
}
private void SetVolume( float val )
{
Volume = val;
if ( ModeController.Instance != null )
{
ModeController.Instance.MasterVolume = val;
}
}
private void OnToggleLanguage()
{
if ( IsLoading ) return;
if ( ModeController.Instance != null )
{
ModeController.Instance.ToggleLanguage();
}
}
private string GetText( string key )
{
return ModeController.Instance?.GetText( key ) ?? key;
}
}
Game
game
using Sandbox;
using System;
public sealed class MetaProgression : Component
{
[Property] public int Credits { get; private set; } = 0;
// Уровни мета-прокачки
[Property] public int ForceLevel { get; private set; } = 0;
[Property] public int MassLevel { get; private set; } = 0;
[Property] public int EnergyLevel { get; private set; } = 0;
// Престиж
[Property] public int PrestigeLevel { get; private set; } = 0;
[Property] public int PrestigeTokens { get; private set; } = 0;
// Требования для сброса (сумма уровней прокачки)
public const int REQUIRED_TOTAL_LEVELS_FOR_PRESTIGE = 15;
private const string SAVE_FILE = "one_more_push_meta.json";
protected override void OnStart()
{
LoadProgress();
}
public void AddCredits( int amount )
{
// Множитель престижа (+50% за каждый уровень престижа)
float multiplier = 1.0f + (PrestigeLevel * 0.5f);
Credits += (int)(amount * multiplier);
SaveProgress();
}
public int GetUpgradeCost( int currentLevel )
{
return (int)(100 * Math.Pow( 1.5, currentLevel ));
}
public bool TryUpgradeForce()
{
int cost = GetUpgradeCost( ForceLevel );
if ( Credits >= cost )
{
Credits -= cost;
ForceLevel++;
SaveProgress();
return true;
}
return false;
}
public bool TryUpgradeMass()
{
int cost = GetUpgradeCost( MassLevel );
if ( Credits >= cost )
{
Credits -= cost;
MassLevel++;
SaveProgress();
return true;
}
return false;
}
public bool TryUpgradeEnergy()
{
int cost = GetUpgradeCost( EnergyLevel );
if ( Credits >= cost )
{
Credits -= cost;
EnergyLevel++;
SaveProgress();
return true;
}
return false;
}
// Проверка: готов ли игрок к престижу
public bool CanPrestige()
{
int totalLevels = ForceLevel + MassLevel + EnergyLevel;
return totalLevels >= REQUIRED_TOTAL_LEVELS_FOR_PRESTIGE;
}
// Сброс прогресса ради Престижа
public bool TryPrestige()
{
if ( !CanPrestige() ) return false;
// Сбрасываем обычные уровни и кредиты
ForceLevel = 0;
MassLevel = 0;
EnergyLevel = 0;
Credits = 0;
// Повышаем уровень престижа и выдаем токены
PrestigeLevel++;
PrestigeTokens += 1;
SaveProgress();
return true;
}
public void SaveProgress()
{
var data = new ProgressData
{
Credits = this.Credits,
ForceLevel = this.ForceLevel,
MassLevel = this.MassLevel,
EnergyLevel = this.EnergyLevel,
PrestigeLevel = this.PrestigeLevel,
PrestigeTokens = this.PrestigeTokens
};
FileSystem.Data.WriteJson( SAVE_FILE, data );
}
public void LoadProgress()
{
if ( FileSystem.Data.FileExists( SAVE_FILE ) )
{
var data = FileSystem.Data.ReadJson<ProgressData>( SAVE_FILE );
if ( data != null )
{
Credits = data.Credits;
ForceLevel = data.ForceLevel;
MassLevel = data.MassLevel;
EnergyLevel = data.EnergyLevel;
PrestigeLevel = data.PrestigeLevel;
PrestigeTokens = data.PrestigeTokens;
}
}
}
private class ProgressData
{
public int Credits { get; set; }
public int ForceLevel { get; set; }
public int MassLevel { get; set; }
public int EnergyLevel { get; set; }
public int PrestigeLevel { get; set; }
public int PrestigeTokens { get; set; }
}
}
Game
game
using Sandbox;
public enum GameMode
{
SumoPush,
NextbotRun
}
public enum Language
{
English,
Russian
}
public sealed class ModeController : Component
{
public static ModeController Instance { get; private set; }
[Property] public GameMode SelectedMode { get; set; } = GameMode.SumoPush;
[Property] public Language CurrentLanguage { get; set; } = Language.Russian;
[Property] public float MasterVolume { get; set; } = 1.0f;
[Property] public SceneFile SumoScene { get; set; }
[Property] public SceneFile NextbotScene { get; set; }
protected override void OnAwake()
{
Instance = this;
}
public void SelectMode( GameMode mode )
{
SelectedMode = mode;
if ( mode == GameMode.SumoPush )
{
if ( SumoScene != null )
Game.ActiveScene.Load( SumoScene );
else
Game.ActiveScene.LoadFromFile( "scenes/onemore.scene" );
}
else if ( mode == GameMode.NextbotRun )
{
if ( NextbotScene != null )
Game.ActiveScene.Load( NextbotScene );
else
Game.ActiveScene.LoadFromFile( "scenes/onemores2.scene" );
}
}
public void ToggleLanguage()
{
CurrentLanguage = CurrentLanguage == Language.Russian ? Language.English : Language.Russian;
}
public string GetText( string key )
{
bool isRu = CurrentLanguage == Language.Russian;
return key switch
{
"game_title" => "ONE MORE PUSH",
"title" => isRu ? "ВЫБЕРИТЕ РЕЖИМ" : "SELECT GAME MODE",
"subtitle" => isRu ? "Выберите режим и отправляйтесь на арену" : "Choose your mode and jump into action",
"sumo_title" => "SUMO PUSH",
"sumo_tag" => isRu ? "КЛАССИКА" : "CLASSIC",
"sumo_desc" => isRu ? "Толкай врагов и ботов с платформы, пока она сужается!" : "Push enemies and bots off the platform before it shrinks!",
"nextbot_title" => "NEXTBOT RUN",
"nextbot_tag" => isRu ? "СКОРО" : "COMING SOON",
"nextbot_desc" => isRu ? "Убегай от некстботов, спасай нокаутированных союзников и выживай!" : "Run from nextbots, revive fallen teammates, and survive!",
"lang_btn" => isRu ? "RU" : "EN",
"settings_btn" => isRu ? "НАСТРОЙКИ" : "SETTINGS",
"exit_btn" => isRu ? "ВЫХОД" : "EXIT",
"settings_title" => isRu ? "НАСТРОЙКИ" : "SETTINGS",
"volume_label" => isRu ? "Громкость звука" : "Master Volume",
"controls_title" => isRu ? "Управление" : "Controls",
"ctrl_move" => isRu ? "WASD — Перемещение" : "WASD — Movement",
"ctrl_action" => isRu ? "ЛКМ / E — Атака / Спасение" : "LMB / E — Push / Revive",
"ctrl_space" => isRu ? "Пробел — Прыжок" : "Space — Jump",
"close_btn" => isRu ? "ЗАКРЫТЬ" : "CLOSE",
"news_title" => isRu ? "НОВОСТИ И ОБНОВЛЕНИЯ" : "NEWS & UPDATES",
"news_tag_new" => isRu ? "ПАТЧ 1.2" : "PATCH 1.2",
"news_item1_title" => isRu ? "Режим Nextbot Run" : "Nextbot Run Mode",
"news_item1_desc" => isRu ? "Режим находится в разработке и скоро будет доступен!" : "Mode in development and coming soon!",
"news_item2_title" => isRu ? "Обновлённый Glass UI" : "New Glass UI",
"news_item2_desc" => isRu ? "Современный интерфейс, настройки звука, выбор языка и экран загрузки." : "Sleek glass UI design, audio settings, multi-language support & loading screen.",
"news_item3_title" => isRu ? "Баланс Sumo Push" : "Sumo Push Balance",
"news_item3_desc" => isRu ? "Улучшена физика толчков и ускорена динамика сужения платформы!" : "Improved push physics and faster platform shrinking dynamics!",
"loading_title" => isRu ? "ЗАГРУЗКА АРЕНЫ..." : "LOADING ARENA...",
"loading_sub" => isRu ? "Подготовка геометрии и генерация объектов..." : "Preparing geometry and spawning objects...",
"tip_label" => isRu ? "СОВЕТ:" : "TIP:",
"tip_sumo" => isRu ? "Следите за мигающими блоками платформы — они упадут первыми!" : "Watch out for flashing platform blocks — they fall first!",
"tip_nextbot" => isRu ? "Держаться вместе с союзниками проще: вы сможете быстрее поднять друг друга!" : "Stick together with teammates: reviving is much faster when close!",
_ => key
};
}
}
Debug: View Raw JSON Response
{
"TotalCount": 51,
"Files": [
{
"Ident": "cherkbyte.one_more_push",
"Path": "BotAI.cs",
"FileName": "BotAI.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\nusing System.Linq;\r\n\r\npublic sealed class BotAI : Component\r\n{\r\n [Property] public float Speed { get; set; } = 220f;\r\n [Property] public float AttackRange { get; set; } = 220f;\r\n\r\n private CharacterController _cc;\r\n private PushAbility _push;\r\n private ManeuverAbility _maneuver;\r\n private TimeUntil _nextDecision;\r\n private Vector3 _moveDir;\r\n\r\n protected override void OnAwake()\r\n {\r\n _cc = Components.Get<CharacterController>();\r\n _push = Components.Get<PushAbility>();\r\n _maneuver = Components.Get<ManeuverAbility>();\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n if ( IsProxy ) return;\r\n\r\n var target = FindTarget();\r\n\r\n if ( target.IsValid() )\r\n {\r\n _moveDir = (target.WorldPosition - WorldPosition).WithZ( 0 ).Normal;\r\n\r\n if ( _cc.IsValid() )\r\n {\r\n _cc.Velocity = _moveDir * Speed;\r\n WorldRotation = Rotation.LookAt( _moveDir );\r\n }\r\n\r\n float dist = Vector3.DistanceBetween( WorldPosition, target.WorldPosition );\r\n if ( dist <= AttackRange && _push.IsValid() && _push.IsReady )\r\n {\r\n _push.TryExecuteBotPush();\r\n }\r\n }\r\n else\r\n {\r\n if ( _nextDecision )\r\n {\r\n _nextDecision = Game.Random.Float( 2f, 4f );\r\n float angle = Game.Random.Float( 0f, MathF.PI * 2f );\r\n _moveDir = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f );\r\n }\r\n\r\n if ( _cc.IsValid() )\r\n {\r\n _cc.Velocity = _moveDir * (Speed * 0.6f);\r\n }\r\n }\r\n\r\n if ( _cc.IsValid() && !_cc.IsOnGround && _maneuver.IsValid() && _maneuver.IsDashReady )\r\n {\r\n _maneuver.TryExecuteBotDash( -_cc.Velocity.Normal );\r\n }\r\n }\r\n\r\n private GameObject FindTarget()\r\n {\r\n return Scene.GetAllComponents<CharacterController>()\r\n .Where( c => c.GameObject != GameObject && c.GameObject.IsValid() )\r\n .OrderBy( c => Vector3.DistanceBetween( WorldPosition, c.WorldPosition ) )\r\n .FirstOrDefault()?.GameObject;\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "BouncyShield.cs",
"FileName": "BouncyShield.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic sealed class BouncyShield : Component, Component.ITriggerListener\r\n{\r\n\t[Property] public float BounceForce { get; set; } = 1500f;\r\n\t[Property] public SoundEvent BounceSound { get; set; }\r\n\r\n\tpublic void OnTriggerEnter( Collider other )\r\n\t{\r\n\t\tvar rootObj = other.GameObject.Root;\r\n\t\tvar pushDir = (rootObj.WorldPosition - WorldPosition).Normal;\r\n\t\tpushDir.z = 0.3f;\r\n\r\n\t\tbool bounced = false;\r\n\r\n\t\tvar cc = rootObj.Components.Get<CharacterController>();\r\n\t\tif ( cc.IsValid() )\r\n\t\t{\r\n\t\t\tcc.Velocity = pushDir * BounceForce;\r\n\t\t\tbounced = true;\r\n\t\t}\r\n\r\n\t\tvar rb = rootObj.Components.Get<Rigidbody>();\r\n\t\tif ( rb.IsValid() )\r\n\t\t{\r\n\t\t\trb.Velocity = pushDir * BounceForce;\r\n\t\t\tbounced = true;\r\n\t\t}\r\n\r\n\t\tif ( bounced )\r\n\t\t{\r\n\t\t\tif ( BounceSound != null )\r\n\t\t\t{\r\n\t\t\t\tSound.Play( BounceSound, WorldPosition );\r\n\t\t\t}\r\n\t\t\tCameraJuice.Instance?.Shake( 12f );\r\n\t\t}\r\n\t}\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "EmoteWheel.cs",
"FileName": "EmoteWheel.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic sealed class EmoteWheel : Component\r\n{\r\n\t[Property] public string CurrentEmote { get; private set; } = \"\";\r\n\t[Property] public float EmoteDuration { get; set; } = 2.5f;\r\n\r\n\tprivate TimeSince _emoteTimer;\r\n\r\n\tpublic void PlayEmote( string emoteIcon )\r\n\t{\r\n\t\tCurrentEmote = emoteIcon;\r\n\t\t_emoteTimer = 0;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( !string.IsNullOrEmpty( CurrentEmote ) && _emoteTimer > EmoteDuration )\r\n\t\t{\r\n\t\t\tCurrentEmote = \"\";\r\n\t\t}\r\n\t}\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "PlayerHealth.cs",
"FileName": "PlayerHealth.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\nusing System.Linq;\r\n\r\npublic sealed class PlayerHealth : Component\r\n{\r\n\t[Property] public float MaxHealth { get; set; } = 100f;\r\n\t[Property] public float CurrentHealth { get; private set; } = 100f;\r\n\r\n\tpublic bool IsDead => CurrentHealth <= 0f;\r\n\tpublic float RespawnTimer { get; private set; } = 0f;\r\n\tpublic float RespawnDuration { get; set; } = 3f;\r\n\r\n\tprivate Vector3 _lastDamageDirection = Vector3.Zero;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tCurrentHealth = MaxHealth;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( IsDead )\r\n\t\t{\r\n\t\t\tRespawnTimer -= Time.Delta;\r\n\t\t\tif ( RespawnTimer <= 0f )\r\n\t\t\t{\r\n\t\t\t\tRespawn();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void TakeDamage( float damage )\r\n\t{\r\n\t\tTakeDamage( damage, Vector3.Zero );\r\n\t}\r\n\r\n\tpublic void TakeDamage( float damage, Vector3 direction )\r\n\t{\r\n\t\tif ( IsDead ) return;\r\n\r\n\t\t_lastDamageDirection = direction;\r\n\t\tCurrentHealth = MathX.Clamp( CurrentHealth - damage, 0f, MaxHealth );\r\n\r\n\t\tif ( CurrentHealth <= 0f )\r\n\t\t{\r\n\t\t\tDie();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Heal( float amount )\r\n\t{\r\n\t\tif ( IsDead ) return;\r\n\t\tCurrentHealth = MathX.Clamp( CurrentHealth + amount, 0f, MaxHealth );\r\n\t}\r\n\r\n\tprivate void Die()\r\n\t{\r\n\t\tRespawnTimer = RespawnDuration;\r\n\r\n\t\tif ( ScoreManager.Instance != null )\r\n\t\t{\r\n\t\t\tScoreManager.Instance.AddDeath();\r\n\t\t}\r\n\r\n\t\tvar ragdoll = Components.Get<PlayerRagdoll>();\r\n\t\tif ( ragdoll.IsValid() )\r\n\t\t{\r\n\t\t\tragdoll.SpawnRagdoll( _lastDamageDirection * 2500f );\r\n\t\t}\r\n\r\n\t\tvar skm = Components.Get<SkinnedModelRenderer>();\r\n\t\tif ( skm.IsValid() ) skm.Enabled = false;\r\n\r\n\t\tvar mr = Components.Get<ModelRenderer>();\r\n\t\tif ( mr.IsValid() ) mr.Enabled = false;\r\n\r\n\t\tvar cc = Components.Get<CharacterController>();\r\n\t\tif ( cc.IsValid() ) cc.Enabled = false;\r\n\r\n\t\tvar rb = Components.Get<Rigidbody>();\r\n\t\tif ( rb.IsValid() ) rb.Enabled = false;\r\n\r\n\t\tCameraJuice.Instance?.Shake( 30f, 6f );\r\n\r\n\t\tLog.Info( \"[PlayerHealth] \u0418\u0433\u0440\u043e\u043a \u043f\u043e\u0433\u0438\u0431!\" );\r\n\t}\r\n\r\n\tpublic void Respawn()\r\n\t{\r\n\t\tCurrentHealth = MaxHealth;\r\n\r\n\t\tvar skm = Components.Get<SkinnedModelRenderer>();\r\n\t\tif ( skm.IsValid() ) skm.Enabled = true;\r\n\r\n\t\tvar mr = Components.Get<ModelRenderer>();\r\n\t\tif ( mr.IsValid() ) mr.Enabled = true;\r\n\r\n\t\tvar cc = Components.Get<CharacterController>();\r\n\t\tif ( cc.IsValid() ) cc.Enabled = true;\r\n\r\n\t\tvar rb = Components.Get<Rigidbody>();\r\n\t\tif ( rb.IsValid() ) rb.Enabled = true;\r\n\r\n\t\tvar spawnPoint = Scene.GetAllComponents<SpawnPoint>().FirstOrDefault();\r\n\t\tif ( spawnPoint.IsValid() )\r\n\t\t{\r\n\t\t\tTransform.World = spawnPoint.Transform.World;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tTransform.LocalPosition = Vector3.Up * 50f;\r\n\t\t}\r\n\r\n\t\tLog.Info( \"[PlayerHealth] \u0418\u0433\u0440\u043e\u043a \u0432\u043e\u0437\u0440\u043e\u0434\u0438\u043b\u0441\u044f!\" );\r\n\t}\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "PushHud.razor",
"FileName": "PushHud.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "@using Sandbox;\r\n@using Sandbox.UI;\r\n@using Sandbox.Services;\r\n@using System;\r\n@using System.Linq;\r\n@using System.Collections.Generic;\r\n@using System.Threading.Tasks;\r\n@inherits PanelComponent\r\n@attribute [StyleSheet( \"PushHud.scss\" )]\r\n\r\n<root class=\"hud-root\">\r\n\r\n <div class=\"top-header-container\">\r\n <div class=\"score-box\">\r\n <div class=\"score-card time\">\r\n <label class=\"score-title\">@L( \"\u0412\u0440\u0435\u043c\u044f\", \"Time\" )</label>\r\n <label class=\"score-value\">@GetFormattedTime()</label>\r\n </div>\r\n <div class=\"score-card\">\r\n <label class=\"score-title\">@L( \"\u0421\u0431\u0438\u0442\u043e\", \"Score\" )</label>\r\n <label class=\"score-value\">@GetScore()</label>\r\n </div>\r\n <div class=\"score-card death\">\r\n <label class=\"score-title\">@L( \"\u0421\u043c\u0435\u0440\u0442\u0438\", \"Deaths\" )</label>\r\n <label class=\"score-value\">@GetDeaths()</label>\r\n </div>\r\n <div class=\"score-card highlight\">\r\n <label class=\"score-title\">@L( \"\u0420\u0435\u043a\u043e\u0440\u0434\", \"Best\" )</label>\r\n <label class=\"score-value\">@GetHighScore()</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"round-badge\">\r\n @L( \"\u0420\u0410\u0423\u041d\u0414\", \"ROUND\" ) @(RoundManager.Instance?.CurrentRound ?? 1)\r\n </div>\r\n\r\n @if ( MapEventManager.Instance != null && MapEventManager.Instance.CurrentEvent != WeatherType.None )\r\n {\r\n <div class=\"weather-banner @GetWeatherClass( MapEventManager.Instance.CurrentEvent )\">\r\n <label class=\"weather-icon\">@GetWeatherIcon( MapEventManager.Instance.CurrentEvent )</label>\r\n <div class=\"weather-info\">\r\n <label class=\"weather-title\">@GetWeatherTitle( MapEventManager.Instance.CurrentEvent )</label>\r\n <label class=\"weather-sub\">@L( \"\u0410\u041a\u0422\u0418\u0412\u041d\u0410\u042f \u0410\u041d\u041e\u041c\u0410\u041b\u0418\u042f\", \"ACTIVE ANOMALY\" )</label>\r\n </div>\r\n <div class=\"weather-timer-badge\">\r\n <label class=\"timer-text\">@($\"{MapEventManager.Instance.TimeRemaining:F1}s\")</label>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"scoreboard-box\">\r\n <div class=\"scoreboard-header\">\r\n <label class=\"header-title\">@L( \"\u0418\u0413\u0420\u041e\u041a\u0418\", \"PLAYERS\" )</label>\r\n </div>\r\n <div class=\"player-list\">\r\n @foreach ( var p in GetPlayerList() )\r\n {\r\n <div class=\"player-row @(p.IsLocal ? \"local\" : \"\")\">\r\n <label class=\"player-name\">@p.Name</label>\r\n <label class=\"player-score\">@p.Score</label>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"killfeed-container\">\r\n @if ( Killfeed.Instance != null )\r\n {\r\n @foreach ( var entry in Killfeed.Instance.Entries )\r\n {\r\n <div class=\"kill-entry\">\r\n <label class=\"killer\">@entry.KillerName</label>\r\n <label class=\"icon\">\ud83d\udca5 [@entry.Method]</label>\r\n <label class=\"victim\">@entry.VictimName</label>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <div class=\"hotbar-container\">\r\n @if ( ShopManager.Instance != null )\r\n {\r\n @foreach ( var item in ShopManager.Instance.Items )\r\n {\r\n bool isSelected = item == ShopManager.Instance.SelectedItem;\r\n \r\n <div class=\"slot-wrapper\">\r\n <div class=\"hotbar-slot @(isSelected ? \"selected\" : \"\") @(!item.IsUnlocked ? \"locked\" : \"\")\" \r\n onclick=\"@(() => ShopManager.Instance.TryBuyOrSelect(item))\">\r\n \r\n <label class=\"slot-name\">@item.GetName( CurrentLanguage )</label>\r\n \r\n @if ( !item.IsUnlocked )\r\n {\r\n <label class=\"slot-cost\">\ud83d\udcb0 @item.Cost</label>\r\n }\r\n </div>\r\n <div class=\"key-badge @(isSelected ? \"selected\" : \"\")\">@item.Key</div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <div class=\"top-left-box\">\r\n <div class=\"chat-box @(IsChatOpen ? \"open\" : \"\")\">\r\n <div class=\"chat-messages\">\r\n @foreach ( var msg in CanvasMessages )\r\n {\r\n <div class=\"chat-row\">\r\n <label class=\"chat-author\">@msg.Author:</label>\r\n <label class=\"chat-text\">@msg.Text</label>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if ( IsChatOpen )\r\n {\r\n <div class=\"chat-input-row\">\r\n <textentry @ref=\"ChatInput\" onsubmit=\"@SendChatMessage\" placeholder=\"@L( \"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435...\", \"Type a message...\" )\"></textentry>\r\n </div>\r\n }\r\n else\r\n {\r\n <div class=\"hints-row\">\r\n <label class=\"chat-hint\" onclick=\"@OpenChat\">@L( \"\u041d\u0430\u0436\u043c\u0438\u0442\u0435 [Enter] \u0434\u043b\u044f \u0447\u0430\u0442\u0430\", \"Press [Enter] to chat\" )</label>\r\n <label class=\"chat-hint prop-hint\" onclick=\"@ToggleWorkshopMenu\">@L( \"[E] \u0421\u043f\u0430\u0432\u043d-\u043c\u0435\u043d\u044e | [R] \u041c\u0430\u0441\u0442\u0435\u0440\u0441\u043a\u0430\u044f\", \"[E] Spawnmenu | [R] Workshop\" )</label>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"player-status-panel\">\r\n <div class=\"status-box health-box\">\r\n <div class=\"status-header\">\r\n <label class=\"status-title\">\u2764\ufe0f @L( \"\u0417\u0414\u041e\u0420\u041e\u0412\u042c\u0415\", \"HEALTH\" )</label>\r\n <label class=\"status-val\">@($\"{GetHealth():F0}\") / @($\"{GetMaxHealth():F0}\")</label>\r\n </div>\r\n <div class=\"bar-background\">\r\n <div class=\"bar-fill health\" style=\"width: @($\"{GetHealthPercent():F1}%\");\"></div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"status-box cooldown-box\">\r\n <div class=\"status-header\">\r\n <label class=\"status-title\">\u26a1 @L( \"\u041f\u0415\u0420\u0415\u0417\u0410\u0420\u042f\u0414\u041a\u0410\", \"COOLDOWN\" )</label>\r\n </div>\r\n <div class=\"bar-background\">\r\n <div class=\"bar-fill\" style=\"width: @($\"{GetPercent():F1}%\");\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if ( PushAbility != null && PushAbility.IsCharging )\r\n {\r\n <div class=\"charge-box\">\r\n <label class=\"charge-title\">@L( \"\u0417\u0410\u0420\u042f\u0414\", \"CHARGE\" )</label>\r\n <div class=\"bar-background\">\r\n <div class=\"bar-fill charge\" style=\"width: @($\"{PushAbility.ChargeProgress * 100f:F1}%\");\"></div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( LocalHealth != null && LocalHealth.IsDead )\r\n {\r\n <div class=\"respawn-overlay\">\r\n <div class=\"respawn-card\">\r\n <label class=\"death-title\">\ud83d\udc80 @L( \"\u0412\u042b \u041f\u041e\u0413\u0418\u0411\u041b\u0418!\", \"YOU DIED!\" )</label>\r\n <label class=\"respawn-subtitle\">\r\n @L( \"\u0412\u043e\u0437\u0440\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437:\", \"Respawning in:\" )\r\n </label>\r\n <label class=\"respawn-timer\">@($\"{Math.Max( 0, LocalHealth.RespawnTimer ):F1}s\")</label>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( !IsMenuOpen && !IsSettingsOpen && !IsWorkshopMenuOpen && !IsQSpawnMenuOpen )\r\n {\r\n <button class=\"side-settings-btn\" onclick=\"@OpenMenu\">\r\n <label class=\"key-hint\">[ESC]</label>\r\n <label class=\"icon\">\u2699</label>\r\n </button>\r\n }\r\n\r\n @if ( IsQSpawnMenuOpen )\r\n {\r\n <div class=\"spawnmenu-overlay\" onclick=\"@CloseQSpawnMenu\">\r\n <div class=\"spawnmenu-window\" onclick=\"@((e) => e.StopPropagation())\">\r\n \r\n <div class=\"spawnmenu-topbar\">\r\n <div class=\"brand-badge\">\r\n <label class=\"brand-text\">ONE MORE PUSH</label>\r\n <label class=\"sub-text\">SPAWN MENU</label>\r\n </div>\r\n\r\n <div class=\"topbar-nav\">\r\n <button class=\"topbar-tab active\">\ud83d\udce6 @L( \"\u041f\u0420\u041e\u041f\u0421\u042b \u0418 \u0410\u0420\u0415\u041d\u0410\", \"PROPS & ARENA\" )</button>\r\n <button class=\"topbar-tab\" onclick=\"@ToggleWorkshopMenu\">\u26a1 @L( \"\u041c\u0410\u0421\u0422\u0415\u0420\u0421\u041a\u0410\u042f [R]\", \"WORKSHOP [R]\" )</button>\r\n </div>\r\n\r\n <div class=\"topbar-credits\">\r\n <label class=\"credits-label\">@L( \"\u0411\u0410\u041b\u0410\u041d\u0421:\", \"BALANCE:\" )</label>\r\n <label class=\"credits-val\">\ud83d\udcb0 @GetCredits()</label>\r\n </div>\r\n </div>\r\n\r\n <div class=\"spawnmenu-body\">\r\n <div class=\"spawnmenu-sidebar\">\r\n <label class=\"sidebar-title\">@L( \"\u041a\u0410\u0422\u0415\u0413\u041e\u0420\u0418\u0418\", \"CATEGORIES\" )</label>\r\n <div class=\"sidebar-item active\">\ud83d\udca5 @L( \"\u0418\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\", \"Interactive\" )</div>\r\n <div class=\"sidebar-item\">\ud83d\udee2\ufe0f @L( \"\u041e\u043f\u0430\u0441\u043d\u044b\u0435 \u0437\u043e\u043d\u044b\", \"Hazard Zones\" )</div>\r\n <div class=\"sidebar-item\">\u2693 @L( \"\u0422\u044f\u0436\u0435\u043b\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b\", \"Heavy Objects\" )</div>\r\n <div class=\"sidebar-item\">\ud83c\udf00 @L( \"\u041b\u043e\u0432\u0443\u0448\u043a\u0438 & \u0411\u0430\u0442\u0443\u0442\u044b\", \"Traps & Springs\" )</div>\r\n </div>\r\n\r\n <div class=\"spawnmenu-content\">\r\n <div class=\"content-header\">\r\n <label class=\"section-title\">@L( \"\u0421\u041f\u0410\u0412\u041d \u041e\u0411\u042a\u0415\u041a\u0422\u041e\u0412\", \"SPAWN OBJECTS\" )</label>\r\n <label class=\"section-desc\">@L( \"\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u0435\u0434\u043c\u0435\u0442 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u0430 \u043a\u0430\u0440\u0442\u0435\", \"Select an item to spawn on the map\" )</label>\r\n </div>\r\n\r\n <div class=\"gmod-prop-grid\">\r\n <div class=\"gmod-prop-card\" onclick=\"@(() => SelectAndSpawnProp(0))\">\r\n <div class=\"card-glow\"></div>\r\n <div class=\"prop-icon\">\ud83d\udca5</div>\r\n <label class=\"prop-label\">@L( \"\u0412\u0437\u0440\u044b\u0432\u043d\u0430\u044f \u0411\u043e\u0447\u043a\u0430\", \"Explosive Barrel\" )</label>\r\n <label class=\"prop-price\">\ud83d\udcb0 15</label>\r\n </div>\r\n\r\n <div class=\"gmod-prop-card\" onclick=\"@(() => SelectAndSpawnProp(1))\">\r\n <div class=\"card-glow\"></div>\r\n <div class=\"prop-icon\">\ud83d\udee2\ufe0f</div>\r\n <label class=\"prop-label\">@L( \"\u0417\u043e\u043d\u0430 \u0421\u043c\u043e\u043b\u044b\", \"Tar Zone\" )</label>\r\n <label class=\"prop-price\">\ud83d\udcb0 20</label>\r\n </div>\r\n\r\n <div class=\"gmod-prop-card\" onclick=\"@(() => SelectAndSpawnProp(2))\">\r\n <div class=\"card-glow\"></div>\r\n <div class=\"prop-icon\">\u2693</div>\r\n <label class=\"prop-label\">@L( \"\u041d\u0430\u043a\u043e\u0432\u0430\u043b\u044c\u043d\u044f\", \"Anvil\" )</label>\r\n <label class=\"prop-price\">\ud83d\udcb0 25</label>\r\n </div>\r\n\r\n <div class=\"gmod-prop-card\" onclick=\"@(() => SelectAndSpawnProp(3))\">\r\n <div class=\"card-glow\"></div>\r\n <div class=\"prop-icon\">\ud83c\udf00</div>\r\n <label class=\"prop-label\">@L( \"\u041f\u0440\u0443\u0436\u0438\u043d\u043d\u044b\u0439 \u0411\u0430\u0442\u0443\u0442\", \"Spring Pad\" )</label>\r\n <label class=\"prop-price\">\ud83d\udcb0 20</label>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"spawnmenu-tools\">\r\n <label class=\"tools-title\">@L( \"\u0418\u041d\u0421\u0422\u0420\u0423\u041c\u0415\u041d\u0422\u042b\", \"TOOLS\" )</label>\r\n \r\n <div class=\"tool-group\">\r\n <label class=\"group-title\">@L( \"\u0412\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\", \"Interaction\" )</label>\r\n <div class=\"tool-item active\">\ud83d\udc49 @L( \"\u0421\u043f\u0430\u0432\u043d-\u043c\u0435\u043d\u044e [E]\", \"Spawn Menu [E]\" )</div>\r\n <div class=\"tool-item\">\ud83d\ude80 @L( \"\u0421\u0438\u043b\u043e\u0432\u0430\u044f \u0432\u043e\u043b\u043d\u0430\", \"Force Wave\" )</div>\r\n </div>\r\n\r\n <div class=\"tool-group\">\r\n <label class=\"group-title\">@L( \"\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\", \"Controls\" )</label>\r\n <div class=\"tool-hint\">@L( \"\u041e\u0442\u043f\u0443\u0441\u0442\u0438\u0442\u0435 [E] \u0434\u043b\u044f \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u0435\u043d\u044e\", \"Release [E] to close menu\" )</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( IsWorkshopMenuOpen )\r\n {\r\n <div class=\"menu-overlay\" onclick=\"@CloseWorkshopMenu\">\r\n <div class=\"prop-menu-card\" onclick=\"@((e) => e.StopPropagation())\">\r\n \r\n <div class=\"workshop-header\">\r\n <label class=\"menu-title\">@L( \"\u041c\u0410\u0421\u0422\u0415\u0420\u0421\u041a\u0410\u042f \u0410\u0420\u0415\u041d\u042b\", \"ARENA WORKSHOP\" )</label>\r\n <div class=\"credits-badge\">\ud83d\udcb0 @GetCredits()</div>\r\n </div>\r\n\r\n <div class=\"tab-selector\">\r\n <button class=\"tab-btn @(ActiveTab == \"PROPS\" ? \"active\" : \"\")\" onclick=\"@(() => SelectTab(\"PROPS\"))\">\r\n \ud83d\udce6 @L( \"\u041f\u0420\u041e\u041f\u0421\u042b\", \"PROPS\" )\r\n </button>\r\n <button class=\"tab-btn @(ActiveTab == \"UPGRADES\" ? \"active\" : \"\")\" onclick=\"@(() => SelectTab(\"UPGRADES\"))\">\r\n \u26a1 @L( \"\u041f\u0420\u041e\u041a\u0410\u0427\u041a\u0410\", \"UPGRADES\" )\r\n </button>\r\n <button class=\"tab-btn @(ActiveTab == \"WEAPONS\" ? \"active\" : \"\")\" onclick=\"@(() => SelectTab(\"WEAPONS\"))\">\r\n \ud83d\udd2b @L( \"\u041e\u0420\u0423\u0416\u0418\u0415\", \"WEAPONS\" )\r\n </button>\r\n <button class=\"tab-btn @(ActiveTab == \"PRESTIGE\" ? \"active\" : \"\")\" onclick=\"@(() => SelectTab(\"PRESTIGE\"))\">\r\n \ud83d\udc51 @L( \"\u041f\u0420\u0415\u0421\u0422\u0418\u0416\", \"PRESTIGE\" )\r\n </button>\r\n </div>\r\n\r\n @if ( ActiveTab == \"PROPS\" )\r\n {\r\n <div class=\"prop-grid\">\r\n <div class=\"prop-item\" onclick=\"@(() => SelectAndSpawnProp(0))\">\r\n <label class=\"prop-name\">\ud83d\udca5 @L( \"\u0411\u043e\u0447\u043a\u0430\", \"Barrel\" )</label>\r\n <label class=\"prop-cost\">\ud83d\udcb0 15</label>\r\n </div>\r\n <div class=\"prop-item\" onclick=\"@(() => SelectAndSpawnProp(1))\">\r\n <label class=\"prop-name\">\ud83d\udee2\ufe0f @L( \"\u0421\u043c\u043e\u043b\u0430\", \"Tar Zone\" )</label>\r\n <label class=\"prop-cost\">\ud83d\udcb0 20</label>\r\n </div>\r\n <div class=\"prop-item\" onclick=\"@(() => SelectAndSpawnProp(2))\">\r\n <label class=\"prop-name\">\u2693 @L( \"\u041d\u0430\u043a\u043e\u0432\u0430\u043b\u044c\u043d\u044f\", \"Anvil\" )</label>\r\n <label class=\"prop-cost\">\ud83d\udcb0 25</label>\r\n </div>\r\n <div class=\"prop-item\" onclick=\"@(() => SelectAndSpawnProp(3))\">\r\n <label class=\"prop-name\">\ud83c\udf00 @L( \"\u0411\u0430\u0442\u0443\u0442\", \"Spring\" )</label>\r\n <label class=\"prop-cost\">\ud83d\udcb0 20</label>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( ActiveTab == \"UPGRADES\" )\r\n {\r\n <div class=\"upgrades-list\">\r\n <div class=\"upgrade-row\">\r\n <div class=\"info\">\r\n <label class=\"name\">\ud83d\udcaa @L( \"\u0421\u0418\u041b\u0410 \u0423\u0414\u0410\u0420\u0410\", \"PUSH FORCE\" )</label>\r\n <label class=\"level\">LVL @GetForceLevel()</label>\r\n </div>\r\n <button class=\"buy-btn\" onclick=\"@TryUpgradeForce\">\r\n <label class=\"btn-text\">@L( \"\u041a\u0423\u041f\u0418\u0422\u042c\", \"BUY\" )</label>\r\n <label class=\"btn-cost\">@GetForceCost() \ud83d\udcb0</label>\r\n </button>\r\n </div>\r\n\r\n <div class=\"upgrade-row\">\r\n <div class=\"info\">\r\n <label class=\"name\">\ud83c\udfcb\ufe0f @L( \"\u041c\u0410\u0421\u0421\u0410 \u0418\u0413\u0420\u041e\u041a\u0410\", \"PLAYER MASS\" )</label>\r\n <label class=\"level\">LVL @GetMassLevel()</label>\r\n </div>\r\n <button class=\"buy-btn\" onclick=\"@TryUpgradeMass\">\r\n <label class=\"btn-text\">@L( \"\u041a\u0423\u041f\u0418\u0422\u042c\", \"BUY\" )</label>\r\n <label class=\"btn-cost\">@GetMassCost() \ud83d\udcb0</label>\r\n </button>\r\n </div>\r\n\r\n <div class=\"upgrade-row\">\r\n <div class=\"info\">\r\n <label class=\"name\">\u26a1 @L( \"\u042d\u041d\u0415\u0420\u0413\u0418\u042f \u0418 \u0420\u042b\u0412\u041e\u041a\", \"ENERGY RECOVERY\" )</label>\r\n <label class=\"level\">LVL @GetEnergyLevel()</label>\r\n </div>\r\n <button class=\"buy-btn\" onclick=\"@TryUpgradeEnergy\">\r\n <label class=\"btn-text\">@L( \"\u041a\u0423\u041f\u0418\u0422\u042c\", \"BUY\" )</label>\r\n <label class=\"btn-cost\">@GetEnergyCost() \ud83d\udcb0</label>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( ActiveTab == \"WEAPONS\" )\r\n {\r\n <div class=\"upgrades-list\">\r\n @if ( WeaponManager.Instance != null )\r\n {\r\n @foreach ( var w in WeaponManager.Instance.Weapons )\r\n {\r\n bool isEquipped = WeaponManager.Instance.CurrentWeapon == w;\r\n\r\n <div class=\"upgrade-row\">\r\n <div class=\"info\">\r\n <label class=\"name\">@(CurrentLanguage == \"RU\" ? w.NameRu : w.NameEn)</label>\r\n <label class=\"level\">\r\n @(isEquipped ? L( \"\u042d\u041a\u0418\u041f\u0418\u0420\u041e\u0412\u0410\u041d\u041e\", \"EQUIPPED\" ) : (w.IsUnlocked ? L( \"\u041a\u0423\u041f\u041b\u0415\u041d\u041e\", \"UNLOCKED\" ) : L( \"\u0417\u0410\u041a\u0420\u042b\u0422\u041e\", \"LOCKED\" )))\r\n </label>\r\n </div>\r\n\r\n @if ( isEquipped )\r\n {\r\n <button class=\"buy-btn equipped\">\r\n <label class=\"btn-text\">\u2714 @L( \"\u0410\u041a\u0422\u0418\u0412\u041d\u041e\", \"ACTIVE\" )</label>\r\n </button>\r\n }\r\n else if ( w.IsUnlocked )\r\n {\r\n <button class=\"buy-btn equip\" onclick=\"@(() => WeaponManager.Instance.TryBuyOrEquip(w))\">\r\n <label class=\"btn-text\">\u26a1 @L( \"\u0412\u0417\u042f\u0422\u042c\", \"EQUIP\" )</label>\r\n </button>\r\n }\r\n else\r\n {\r\n <button class=\"buy-btn\" onclick=\"@(() => WeaponManager.Instance.TryBuyOrEquip(w))\">\r\n <label class=\"btn-text\">@L( \"\u041a\u0423\u041f\u0418\u0422\u042c\", \"BUY\" )</label>\r\n <label class=\"btn-cost\">@w.Cost \ud83d\udcb0</label>\r\n </button>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n @if ( ActiveTab == \"PRESTIGE\" )\r\n {\r\n <div class=\"prestige-card-container\">\r\n <div class=\"prestige-info-box\">\r\n <label class=\"prestige-lvl\">\ud83d\udc51 @L( \"\u0423\u0420\u041e\u0412\u0415\u041d\u042c \u041f\u0420\u0415\u0421\u0422\u0418\u0416\u0410:\", \"PRESTIGE LEVEL:\" ) @GetPrestigeLevel()</label>\r\n <label class=\"prestige-bonus\">\u2728 @L( \"\u0411\u041e\u041d\u0423\u0421 \u041a \u041a\u0420\u0415\u0414\u0418\u0422\u0410\u041c:\", \"CREDIT BONUS:\" ) +@(GetPrestigeLevel() * 50)%</label>\r\n <label class=\"prestige-tokens\">\ud83d\udd2e @L( \"\u041e\u0427\u041a\u0418 \u041f\u0420\u0415\u0421\u0422\u0418\u0416\u0410:\", \"PRESTIGE TOKENS:\" ) @GetPrestigeTokens()</label>\r\n </div>\r\n\r\n <div class=\"prestige-desc\">\r\n <label class=\"desc-text\">\r\n @L( \"\u0421\u0431\u0440\u043e\u0441\u044c\u0442\u0435 \u0443\u0440\u043e\u0432\u043d\u0438 \u0421\u0438\u043b\u044b, \u041c\u0430\u0441\u0441\u044b \u0438 \u042d\u043d\u0435\u0440\u0433\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u041e\u0447\u043a\u043e \u041f\u0440\u0435\u0441\u0442\u0438\u0436\u0430 \u0438 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u0431\u043e\u043d\u0443\u0441 +50% \u043a \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u043a\u0443!\", \r\n \"Reset Force, Mass, and Energy levels to get a Prestige Token and a permanent +50% credit bonus!\" )\r\n </label>\r\n <label class=\"req-text\">\r\n @L( \"\u0422\u0420\u0415\u0411\u041e\u0412\u0410\u041d\u0418\u0415: \u0421\u0443\u043c\u043c\u0430 \u0443\u0440\u043e\u0432\u043d\u0435\u0439 \u043f\u0440\u043e\u043a\u0430\u0447\u043a\u0438 15+\", \"REQUIREMENT: Total Upgrade Levels 15+\" )\r\n </label>\r\n </div>\r\n\r\n <button class=\"prestige-btn @(CanPrestige() ? \"ready\" : \"disabled\")\" onclick=\"@TryPrestige\">\r\n <label class=\"btn-text\">@L( \"\u0421\u0411\u0420\u041e\u0421\u0418\u0422\u042c \u0418 \u041f\u041e\u041b\u0423\u0427\u0418\u0422\u042c \u041f\u0420\u0415\u0421\u0422\u0418\u0416\", \"PRESTIGE RESET\" )</label>\r\n </button>\r\n </div>\r\n }\r\n\r\n <button class=\"menu-btn\" onclick=\"@CloseWorkshopMenu\">@L( \"\u0417\u0410\u041a\u0420\u042b\u0422\u042c [R]\", \"CLOSE [R]\" )</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( IsMenuOpen )\r\n {\r\n <div class=\"main-menu-screen\">\r\n <div class=\"main-menu-sidebar\">\r\n <div class=\"brand-section\">\r\n <div class=\"logo-glow\"></div>\r\n <label class=\"brand-title\">ONE MORE PUSH</label>\r\n <label class=\"brand-subtitle\">PHYSICS ARENA BRAWLER</label>\r\n </div>\r\n\r\n <div class=\"nav-buttons-group\">\r\n <button class=\"epic-btn play\" onclick=\"@StartGame\">\r\n <div class=\"btn-glow\"></div>\r\n <label class=\"btn-icon\">\u25b6</label>\r\n <div class=\"btn-content\">\r\n <label class=\"btn-title\">@L( \"\u0412 \u0411\u041e\u0419\", \"PLAY NOW\" )</label>\r\n <label class=\"btn-sub\">@L( \"\u0412\u044b\u0439\u0442\u0438 \u043d\u0430 \u0430\u0440\u0435\u043d\u0443\", \"Enter the Arena\" )</label>\r\n </div>\r\n </button>\r\n\r\n <button class=\"epic-btn\" onclick=\"@OpenSettings\">\r\n <label class=\"btn-icon\">\u2699</label>\r\n <div class=\"btn-content\">\r\n <label class=\"btn-title\">@L( \"\u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418\", \"SETTINGS\" )</label>\r\n <label class=\"btn-sub\">@L( \"\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\", \"Game options\" )</label>\r\n </div>\r\n </button>\r\n\r\n <button class=\"epic-btn quit\" onclick=\"@QuitGame\">\r\n <label class=\"btn-icon\">\u2716</label>\r\n <div class=\"btn-content\">\r\n <label class=\"btn-title\">@L( \"\u0412 \u041c\u0415\u041d\u042e\", \"MAIN MENU\" )</label>\r\n <label class=\"btn-sub\">@L( \"\u0412\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e\", \"Return to Main Menu\" )</label>\r\n </div>\r\n </button>\r\n </div>\r\n\r\n <div class=\"player-profile-card\">\r\n <div class=\"profile-icon\">\ud83c\udfae</div>\r\n <div class=\"profile-info\">\r\n <label class=\"profile-name\">@(Connection.Local?.DisplayName ?? L( \"\u0411\u043e\u0435\u0446\", \"Fighter\" ))</label>\r\n <label class=\"profile-stats\">\ud83d\udcb0 @GetCredits() | \ud83c\udfc6 @GetHighScore()</label>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"main-menu-leaderboard-panel\">\r\n <div class=\"lb-header\">\r\n <label class=\"lb-title\">\ud83c\udfc6 @L( \"\u0422\u0410\u0411\u041b\u0418\u0426\u0410 \u041b\u0418\u0414\u0415\u0420\u041e\u0412\", \"GLOBAL LEADERBOARD\" )</label>\r\n <button class=\"lb-refresh-btn\" onclick=\"@RefreshLeaderboard\">\ud83d\udd04</button>\r\n </div>\r\n\r\n <div class=\"lb-tabs\">\r\n <button class=\"tab-btn @(CurrentLbStat == \"pushes_count\" ? \"active\" : \"\")\" onclick=\"@(() => SelectLbStat(\"pushes_count\"))\">\r\n \ud83d\ude80 @L( \"\u0422\u041e\u041b\u0427\u041a\u0418\", \"PUSHES\" )\r\n </button>\r\n <button class=\"tab-btn @(CurrentLbStat == \"rounds_won\" ? \"active\" : \"\")\" onclick=\"@(() => SelectLbStat(\"rounds_won\"))\">\r\n \ud83d\udc51 @L( \"\u041f\u041e\u0411\u0415\u0414\u042b\", \"WINS\" )\r\n </button>\r\n </div>\r\n\r\n <div class=\"lb-body\">\r\n @if ( IsLbLoading )\r\n {\r\n <div class=\"lb-status\">@L( \"\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430...\", \"LOADING...\" )</div>\r\n }\r\n else if ( LbEntries == null || LbEntries.Count == 0 )\r\n {\r\n <div class=\"lb-status\">@L( \"\u041d\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0445\", \"NO RECORD FOUND\" )</div>\r\n }\r\n else\r\n {\r\n <div class=\"lb-table\">\r\n <div class=\"lb-table-header\">\r\n <span class=\"col-rank\">#</span>\r\n <span class=\"col-player\">@L( \"\u0418\u0413\u0420\u041e\u041a\", \"PLAYER\" )</span>\r\n <span class=\"col-score\">@L( \"\u0421\u0427\u0415\u0422\", \"SCORE\" )</span>\r\n </div>\r\n <div class=\"lb-table-rows\">\r\n @foreach ( var entry in LbEntries )\r\n {\r\n <div class=\"lb-row @(entry.SteamId == (Connection.Local?.SteamId ?? 0) ? \"is-me\" : \"\")\">\r\n <span class=\"col-rank\">@GetRankBadge( entry.Rank )</span>\r\n <span class=\"col-player\">@entry.DisplayName</span>\r\n <span class=\"col-score\">@entry.Value.ToString(\"N0\")</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"main-menu-footer-badge\">\r\n <label class=\"footer-ver\">v1.0.0 RELEASE</label>\r\n <label class=\"footer-engine\">POWERED BY SOURCE 2 & s&box</label>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( IsSettingsOpen )\r\n {\r\n <div class=\"main-menu-screen\">\r\n <div class=\"main-menu-sidebar settings-wide\">\r\n <div class=\"brand-section\">\r\n <label class=\"brand-title\">@L( \"\u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418\", \"SETTINGS\" )</label>\r\n <label class=\"brand-subtitle\">CONFIGURATION PANEL</label>\r\n </div>\r\n\r\n <div class=\"settings-list-container\">\r\n <div class=\"setting-row-card\">\r\n <div class=\"setting-info\">\r\n <label class=\"title\">\ud83c\udf10 @L( \"\u042f\u0437\u044b\u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\", \"Language\" )</label>\r\n <label class=\"desc\">@L( \"\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u044f\u0437\u044b\u043a \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\", \"Select UI Language\" )</label>\r\n </div>\r\n <div class=\"lang-selector\">\r\n <button class=\"lang-btn @(CurrentLanguage == \"EN\" ? \"active\" : \"\")\" onclick=\"@(() => SetLanguage(\"EN\"))\">EN</button>\r\n <button class=\"lang-btn @(CurrentLanguage == \"RU\" ? \"active\" : \"\")\" onclick=\"@(() => SetLanguage(\"RU\"))\">RU</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"setting-row-card\">\r\n <div class=\"setting-info\">\r\n <label class=\"title\">\ud83e\udd16 @L( \"\u0421\u043f\u0430\u0432\u043d \u0411\u043e\u0442\u043e\u0432\", \"Bot Spawner\" )</label>\r\n <label class=\"desc\">@L( \"\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0431\u043e\u0442\u043e\u0432 \u043d\u0430 \u043a\u0430\u0440\u0442\u0443\", \"Spawn AI bots in game\" )</label>\r\n </div>\r\n <button class=\"lang-btn @(BotSpawner.EnableBots ? \"active\" : \"\")\" onclick=\"@ToggleBots\">\r\n @(BotSpawner.EnableBots ? L( \"\u0412\u041a\u041b\", \"ON\" ) : L( \"\u0412\u042b\u041a\u041b\", \"OFF\" ))\r\n </button>\r\n </div>\r\n\r\n <div class=\"setting-row-card\">\r\n <div class=\"setting-info\">\r\n <label class=\"title\">\ud83c\udfb5 @L( \"\u0424\u043e\u043d\u043e\u0432\u0430\u044f \u041c\u0443\u0437\u044b\u043a\u0430\", \"Music\" )</label>\r\n <label class=\"desc\">@L( \"\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0430\u0443\u043d\u0434\u0442\u0440\u0435\u043a\", \"Toggle battle music\" )</label>\r\n </div>\r\n <button class=\"lang-btn @(EnableMusic ? \"active\" : \"\")\" onclick=\"@ToggleMusic\">\r\n @(EnableMusic ? L( \"\u0412\u041a\u041b\", \"ON\" ) : L( \"\u0412\u042b\u041a\u041b\", \"OFF\" ))\r\n </button>\r\n </div>\r\n\r\n <div class=\"setting-row-card\">\r\n <div class=\"setting-info\">\r\n <label class=\"title\">\ud83c\udfaf @L( \"\u0427\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\", \"Sensitivity\" )</label>\r\n <label class=\"desc\">@L( \"\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u043e\u0432\u043e\u0440\u043e\u0442\u0430 \u043a\u0430\u043c\u0435\u0440\u044b\", \"Mouse sensitivity factor\" )</label>\r\n </div>\r\n <label class=\"sens-badge\">1.0</label>\r\n </div>\r\n </div>\r\n\r\n <button class=\"epic-btn back\" onclick=\"@CloseSettings\">\r\n <label class=\"btn-icon\">\u2b05</label>\r\n <div class=\"btn-content\">\r\n <label class=\"btn-title\">@L( \"\u041d\u0410\u0417\u0410\u0414\", \"BACK\" )</label>\r\n </div>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n</root>\r\n\r\n@code {\r\n public class EntryItem\r\n {\r\n public int Rank { get; set; }\r\n public string DisplayName { get; set; }\r\n public double Value { get; set; }\r\n public ulong SteamId { get; set; }\r\n }\r\n\r\n [Property] public PushAbility PushAbility { get; set; }\r\n public PlayerHealth LocalHealth { get; private set; }\r\n\r\n public bool IsMenuOpen { get; set; } = true;\r\n public bool IsSettingsOpen { get; set; } = false;\r\n public bool IsWorkshopMenuOpen { get; set; } = false;\r\n public bool IsQSpawnMenuOpen { get; set; } = false;\r\n\r\n public string ActiveTab { get; set; } = \"PROPS\";\r\n public static string CurrentLanguage { get; set; } = \"EN\";\r\n\r\n public bool IsLbLoading { get; set; } = true;\r\n public string CurrentLbStat { get; set; } = \"pushes_count\";\r\n public List<EntryItem> LbEntries { get; set; } = new();\r\n\r\n public struct ChatMessage\r\n {\r\n public string Author { get; set; }\r\n public string Text { get; set; }\r\n }\r\n\r\n public List<ChatMessage> CanvasMessages { get; set; } = new();\r\n public bool IsChatOpen { get; set; } = false;\r\n private TextEntry ChatInput { get; set; }\r\n\r\n public struct PlayerInfo\r\n {\r\n public string Name { get; set; }\r\n public int Score { get; set; }\r\n public bool IsLocal { get; set; }\r\n }\r\n\r\n private string L( string ru, string en ) => CurrentLanguage == \"RU\" ? ru : en;\r\n private bool IsReady => PushAbility?.IsReady ?? false;\r\n\r\n private MetaProgression GetMeta() => Scene.GetAllComponents<MetaProgression>().FirstOrDefault();\r\n private int GetCredits() => GetMeta()?.Credits ?? 0;\r\n private int GetForceLevel() => GetMeta()?.ForceLevel ?? 0;\r\n private int GetMassLevel() => GetMeta()?.MassLevel ?? 0;\r\n private int GetEnergyLevel() => GetMeta()?.EnergyLevel ?? 0;\r\n\r\n private int GetPrestigeLevel() => GetMeta()?.PrestigeLevel ?? 0;\r\n private int GetPrestigeTokens() => GetMeta()?.PrestigeTokens ?? 0;\r\n private bool CanPrestige() => GetMeta()?.CanPrestige() ?? false;\r\n private void TryPrestige() => GetMeta()?.TryPrestige();\r\n\r\n private int GetForceCost() => GetMeta()?.GetUpgradeCost(GetForceLevel()) ?? 0;\r\n private int GetMassCost() => GetMeta()?.GetUpgradeCost(GetMassLevel()) ?? 0;\r\n private int GetEnergyCost() => GetMeta()?.GetUpgradeCost(GetEnergyLevel()) ?? 0;\r\n\r\n private void TryUpgradeForce() => GetMeta()?.TryUpgradeForce();\r\n private void TryUpgradeMass() => GetMeta()?.TryUpgradeMass();\r\n private void TryUpgradeEnergy() => GetMeta()?.TryUpgradeEnergy();\r\n\r\n private float GetHealth() => LocalHealth?.CurrentHealth ?? 100f;\r\n private float GetMaxHealth() => LocalHealth?.MaxHealth ?? 100f;\r\n private float GetHealthPercent() => MathX.Clamp( (GetHealth() / GetMaxHealth()) * 100f, 0f, 100f );\r\n\r\n private void SelectTab( string tab )\r\n {\r\n ActiveTab = tab;\r\n StateHasChanged();\r\n }\r\n\r\n protected override void OnEnabled()\r\n {\r\n base.OnEnabled();\r\n AddMessage( L( \"\u0421\u0438\u0441\u0442\u0435\u043c\u0430\", \"System\" ), L( \"\u0427\u0430\u0442 \u0433\u043e\u0442\u043e\u0432. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 Enter \u0434\u043b\u044f \u0432\u0432\u043e\u0434\u0430.\", \"Chat ready. Press Enter to type.\" ) );\r\n ApplyMouseState();\r\n _ = RefreshLeaderboard();\r\n }\r\n\r\n private async Task SelectLbStat( string statIdent )\r\n {\r\n if ( CurrentLbStat == statIdent ) return;\r\n CurrentLbStat = statIdent;\r\n await RefreshLeaderboard();\r\n }\r\n\r\n private async Task RefreshLeaderboard()\r\n {\r\n IsLbLoading = true;\r\n StateHasChanged();\r\n\r\n try\r\n {\r\n var board = Sandbox.Services.Leaderboards.GetFromStat( \"seilordstudio.one_more_push\", CurrentLbStat );\r\n board.SetAggregationSum();\r\n board.MaxEntries = 10;\r\n await board.Refresh();\r\n\r\n if ( board.Entries != null )\r\n {\r\n LbEntries = board.Entries.Select( x => new EntryItem\r\n {\r\n Rank = Convert.ToInt32( x.Rank ),\r\n DisplayName = x.DisplayName ?? \"Player\",\r\n Value = Convert.ToDouble( x.Value ),\r\n SteamId = Convert.ToUInt64( x.SteamId )\r\n } ).ToList();\r\n }\r\n else\r\n {\r\n LbEntries = new();\r\n }\r\n }\r\n catch ( System.Exception ex )\r\n {\r\n Log.Warning( $\"Leaderboard fetch error: {ex.Message}\" );\r\n }\r\n\r\n IsLbLoading = false;\r\n StateHasChanged();\r\n }\r\n\r\n private string GetRankBadge( int rank )\r\n {\r\n return rank switch\r\n {\r\n 1 => \"\ud83e\udd47 1\",\r\n 2 => \"\ud83e\udd48 2\",\r\n 3 => \"\ud83e\udd49 3\",\r\n _ => rank.ToString()\r\n };\r\n }\r\n\r\n private bool IsEPressed()\r\n {\r\n return Input.Down( \"use\" ) || Input.Down( \"e\" ) || Input.Down( \"E\" );\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n base.OnUpdate();\r\n\r\n if ( PushAbility == null )\r\n {\r\n PushAbility = Scene.GetAllComponents<PushAbility>().FirstOrDefault( x => !x.IsProxy );\r\n }\r\n\r\n if ( LocalHealth == null )\r\n {\r\n LocalHealth = Scene.GetAllComponents<PlayerHealth>().FirstOrDefault( x => !x.IsProxy );\r\n }\r\n\r\n bool eDown = IsEPressed();\r\n if ( eDown )\r\n {\r\n if ( !IsQSpawnMenuOpen && !IsChatOpen && !IsSettingsOpen && !IsMenuOpen )\r\n {\r\n IsWorkshopMenuOpen = false;\r\n IsQSpawnMenuOpen = true;\r\n ApplyMouseState();\r\n StateHasChanged();\r\n }\r\n }\r\n else\r\n {\r\n if ( IsQSpawnMenuOpen )\r\n {\r\n CloseQSpawnMenu();\r\n }\r\n }\r\n\r\n if ( Input.EscapePressed || Input.Pressed( \"menu\" ) )\r\n {\r\n Input.EscapePressed = false;\r\n ToggleMenu();\r\n }\r\n\r\n if ( Input.Pressed( \"reload\" ) || Input.Pressed( \"r\" ) || Input.Pressed( \"R\" ) )\r\n {\r\n if ( !IsMenuOpen && !IsSettingsOpen && !IsChatOpen && !IsQSpawnMenuOpen )\r\n {\r\n ToggleWorkshopMenu();\r\n }\r\n }\r\n\r\n if ( Input.Pressed( \"chat\" ) || Input.Pressed( \"enter\" ) )\r\n {\r\n if ( !IsMenuOpen && !IsSettingsOpen && !IsWorkshopMenuOpen && !IsQSpawnMenuOpen )\r\n {\r\n if ( !IsChatOpen ) OpenChat();\r\n }\r\n }\r\n\r\n ApplyMouseState();\r\n }\r\n\r\n private void SetLanguage( string lang )\r\n {\r\n CurrentLanguage = lang;\r\n StateHasChanged();\r\n }\r\n\r\n private void ApplyMouseState()\r\n {\r\n bool needsMouse = IsMenuOpen || IsSettingsOpen || IsChatOpen || IsWorkshopMenuOpen || IsQSpawnMenuOpen;\r\n Mouse.Visibility = needsMouse ? MouseVisibility.Visible : MouseVisibility.Hidden;\r\n\r\n if ( !needsMouse && Panel != null )\r\n {\r\n Panel.Focus();\r\n }\r\n }\r\n\r\n private void ToggleWorkshopMenu()\r\n {\r\n IsWorkshopMenuOpen = !IsWorkshopMenuOpen;\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n StateHasChanged();\r\n }\r\n\r\n private void CloseWorkshopMenu()\r\n {\r\n IsWorkshopMenuOpen = false;\r\n ApplyMouseState();\r\n StateHasChanged();\r\n }\r\n\r\n private void CloseQSpawnMenu()\r\n {\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n StateHasChanged();\r\n }\r\n\r\n private void SelectAndSpawnProp( int index )\r\n {\r\n if ( PropSpawner.Instance != null )\r\n {\r\n PropSpawner.Instance.SpawnByIndex( index );\r\n }\r\n if ( IsWorkshopMenuOpen ) CloseWorkshopMenu();\r\n }\r\n\r\n private void ToggleMenu()\r\n {\r\n if ( IsSettingsOpen ) CloseSettings();\r\n else if ( IsWorkshopMenuOpen ) CloseWorkshopMenu();\r\n else if ( IsQSpawnMenuOpen ) CloseQSpawnMenu();\r\n else if ( IsMenuOpen ) StartGame();\r\n else OpenMenu();\r\n }\r\n\r\n private void StartGame()\r\n {\r\n IsMenuOpen = false;\r\n IsSettingsOpen = false;\r\n IsWorkshopMenuOpen = false;\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n }\r\n\r\n private void OpenMenu()\r\n {\r\n IsMenuOpen = true;\r\n IsSettingsOpen = false;\r\n IsWorkshopMenuOpen = false;\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n _ = RefreshLeaderboard();\r\n }\r\n\r\n private void OpenSettings()\r\n {\r\n IsMenuOpen = false;\r\n IsSettingsOpen = true;\r\n IsWorkshopMenuOpen = false;\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n }\r\n\r\n private void CloseSettings()\r\n {\r\n IsSettingsOpen = false;\r\n IsMenuOpen = true;\r\n IsWorkshopMenuOpen = false;\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n }\r\n\r\n private void QuitGame()\r\n {\r\n Game.ActiveScene.LoadFromFile( \"scenes/mainmenu.scene\" );\r\n }\r\n\r\n private int GetScore() => ScoreManager.Instance?.Score ?? 0;\r\n\r\n private string GetFormattedTime()\r\n {\r\n var shrinker = Scene.GetAllComponents<ArenaShrinker>().FirstOrDefault();\r\n if ( !shrinker.IsValid() ) return \"02:00\";\r\n\r\n int minutes = (int)Math.Max( 0, shrinker.TimeRemaining ) / 60;\r\n int seconds = (int)Math.Max( 0, shrinker.TimeRemaining ) % 60;\r\n return $\"{minutes:00}:{seconds:00}\";\r\n }\r\n\r\n private float GetPercent()\r\n {\r\n if ( PushAbility == null ) return 0f;\r\n return MathX.Clamp( PushAbility.CooldownProgress * 100f, 0f, 100f );\r\n }\r\n\r\n private void ToggleBots()\r\n {\r\n BotSpawner.EnableBots = !BotSpawner.EnableBots;\r\n StateHasChanged();\r\n }\r\n\r\n private int GetHighScore() => SaveManager.Current?.HighScore ?? 0;\r\n\r\n private void OpenChat()\r\n {\r\n IsChatOpen = true;\r\n IsWorkshopMenuOpen = false;\r\n IsQSpawnMenuOpen = false;\r\n ApplyMouseState();\r\n StateHasChanged();\r\n ChatInput?.Focus();\r\n }\r\n\r\n private void SendChatMessage()\r\n {\r\n if ( ChatInput == null || string.IsNullOrWhiteSpace( ChatInput.Text ) )\r\n {\r\n IsChatOpen = false;\r\n ApplyMouseState();\r\n return;\r\n }\r\n\r\n string name = Connection.Local?.DisplayName ?? L( \"\u0412\u044b\", \"You\" );\r\n AddMessage( name, ChatInput.Text );\r\n\r\n ChatInput.Text = \"\";\r\n IsChatOpen = false;\r\n ApplyMouseState();\r\n }\r\n\r\n public void AddMessage( string author, string text )\r\n {\r\n CanvasMessages.Add( new ChatMessage { Author = author, Text = text } );\r\n if ( CanvasMessages.Count > 6 )\r\n {\r\n CanvasMessages.RemoveAt( 0 );\r\n }\r\n StateHasChanged();\r\n }\r\n\r\n private IEnumerable<PlayerInfo> GetPlayerList()\r\n {\r\n if ( Connection.All != null && Connection.All.Any() )\r\n {\r\n foreach ( var conn in Connection.All )\r\n {\r\n bool isLocal = conn == Connection.Local;\r\n int score = isLocal ? GetScore() : 0;\r\n\r\n yield return new PlayerInfo\r\n {\r\n Name = string.IsNullOrEmpty( conn.DisplayName ) ? \"Player\" : conn.DisplayName,\r\n Score = score,\r\n IsLocal = isLocal\r\n };\r\n }\r\n }\r\n else\r\n {\r\n yield return new PlayerInfo\r\n {\r\n Name = L( \"\u0412\u044b\", \"You\" ),\r\n Score = GetScore(),\r\n IsLocal = true\r\n };\r\n }\r\n }\r\n\r\n private string GetWeatherTitle( WeatherType type ) => type switch\r\n {\r\n WeatherType.LowGravity => L( \"\u041d\u0415\u0412\u0415\u0421\u041e\u041c\u041e\u0421\u0422\u042c\", \"LOW GRAVITY\" ),\r\n WeatherType.SlipperyIce => L( \"\u0413\u041e\u041b\u041e\u041b\u0401\u0414\", \"SLIPPERY ICE\" ),\r\n WeatherType.StrongWind => L( \"\u0428\u0422\u041e\u0420\u041c\u041e\u0412\u041e\u0419 \u0412\u0415\u0422\u0415\u0420\", \"STRONG WIND\" ),\r\n _ => \"\"\r\n };\r\n\r\n private string GetWeatherIcon( WeatherType type ) => type switch\r\n {\r\n WeatherType.LowGravity => \"\ud83d\ude80\",\r\n WeatherType.SlipperyIce => \"\u2744\ufe0f\",\r\n WeatherType.StrongWind => \"\ud83c\udf2a\ufe0f\",\r\n _ => \"\u26a0\ufe0f\"\r\n };\r\n\r\n private string GetWeatherClass( WeatherType type ) => type switch\r\n {\r\n WeatherType.LowGravity => \"gravity\",\r\n WeatherType.SlipperyIce => \"ice\",\r\n WeatherType.StrongWind => \"wind\",\r\n _ => \"\"\r\n };\r\n\r\n private int GetDeaths() => ScoreManager.Instance?.Deaths ?? 0;\r\n public static bool EnableMusic { get; set; } = true;\r\n\r\n private void ToggleMusic()\r\n {\r\n EnableMusic = !EnableMusic;\r\n MusicManager.Instance?.ToggleMusic( EnableMusic );\r\n StateHasChanged();\r\n }\r\n\r\n protected override int BuildHash()\r\n {\r\n var hash = new HashCode();\r\n hash.Add( GetPercent() );\r\n hash.Add( GetHealthPercent() );\r\n hash.Add( LocalHealth?.IsDead );\r\n hash.Add( (int)(LocalHealth?.RespawnTimer * 10f ?? 0) );\r\n hash.Add( IsReady );\r\n hash.Add( MapEventManager.Instance?.CurrentEvent );\r\n hash.Add( (int)(MapEventManager.Instance?.TimeRemaining * 10f ?? 0) );\r\n hash.Add( EnableMusic );\r\n hash.Add( PushAbility?.ChargeProgress );\r\n hash.Add( PushAbility?.IsCharging );\r\n hash.Add( GetScore() );\r\n hash.Add( GetDeaths() ); \r\n hash.Add( GetFormattedTime() );\r\n hash.Add( RoundManager.Instance?.CurrentRound );\r\n hash.Add( IsMenuOpen );\r\n hash.Add( IsWorkshopMenuOpen );\r\n hash.Add( IsQSpawnMenuOpen );\r\n hash.Add( ActiveTab );\r\n hash.Add( GetCredits() );\r\n hash.Add( GetForceLevel() );\r\n hash.Add( GetMassLevel() );\r\n hash.Add( GetEnergyLevel() );\r\n hash.Add( GetPrestigeLevel() );\r\n hash.Add( GetPrestigeTokens() );\r\n hash.Add( CanPrestige() );\r\n hash.Add( WeaponManager.Instance?.CurrentWeapon?.Id );\r\n hash.Add( ShopManager.Instance?.SelectedItem?.Id );\r\n hash.Add( IsSettingsOpen );\r\n hash.Add( CurrentLanguage );\r\n hash.Add( IsChatOpen );\r\n hash.Add( CanvasMessages.Count );\r\n hash.Add( Connection.All?.Count() ?? 0 );\r\n hash.Add( Killfeed.Instance?.Entries.Count );\r\n hash.Add( CurrentLbStat );\r\n hash.Add( IsLbLoading );\r\n hash.Add( LbEntries.Count );\r\n return hash.ToHashCode();\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": ".obj/__compiler_extra.cs",
"FileName": "__compiler_extra.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"One More Push\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"one_more_push\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"cherkbyte\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"cherkbyte.one_more_push\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"29\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v9.0\", FrameworkDisplayName = \".NET 9.0\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"2026-10-06T04:15:32.2273688Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.110.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.110.0\")]"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "ArenaRoundManeger1.cs",
"FileName": "ArenaRoundManeger1.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System.Linq;\r\n\r\npublic enum ArenaRoundState\r\n{\r\n WaitingForPlayers,\r\n Countdown,\r\n InGame,\r\n RoundEnd\r\n}\r\n\r\npublic sealed class ArenaRoundManager : Component\r\n{\r\n [Property] public float CountdownDuration { get; set; } = 5.0f;\r\n [Property] public float RoundDuration { get; set; } = 60.0f;\r\n [Property] public float EndPauseDuration { get; set; } = 4.0f;\r\n\r\n public ArenaRoundState CurrentState { get; private set; } = ArenaRoundState.WaitingForPlayers;\r\n public TimeUntil StateTimer { get; private set; }\r\n\r\n protected override void OnStart()\r\n {\r\n SetState( ArenaRoundState.WaitingForPlayers );\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n switch ( CurrentState )\r\n {\r\n case ArenaRoundState.WaitingForPlayers:\r\n if ( Connection.All.Count >= 1 )\r\n {\r\n SetState( ArenaRoundState.Countdown );\r\n }\r\n break;\r\n\r\n case ArenaRoundState.Countdown:\r\n if ( StateTimer )\r\n {\r\n SetState( ArenaRoundState.InGame );\r\n }\r\n break;\r\n\r\n case ArenaRoundState.InGame:\r\n if ( StateTimer )\r\n {\r\n SetState( ArenaRoundState.RoundEnd );\r\n }\r\n break;\r\n\r\n case ArenaRoundState.RoundEnd:\r\n if ( StateTimer )\r\n {\r\n SetState( ArenaRoundState.WaitingForPlayers );\r\n }\r\n break;\r\n }\r\n }\r\n\r\n private void SetState( ArenaRoundState newState )\r\n {\r\n CurrentState = newState;\r\n\r\n switch ( newState )\r\n {\r\n case ArenaRoundState.WaitingForPlayers:\r\n Log.Info( \"[ArenaRoundManager] \u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435 \u0438\u0433\u0440\u043e\u043a\u043e\u0432...\" );\r\n break;\r\n\r\n case ArenaRoundState.Countdown:\r\n StateTimer = CountdownDuration;\r\n Log.Info( $\"[ArenaRoundManager] \u041e\u0442\u0441\u0447\u0451\u0442! \u0414\u043e \u0441\u0442\u0430\u0440\u0442\u0430: {CountdownDuration} \u0441\u0435\u043a.\" );\r\n break;\r\n\r\n case ArenaRoundState.InGame:\r\n StateTimer = RoundDuration;\r\n Log.Info( \"[ArenaRoundManager] \u0420\u0410\u0423\u041d\u0414 \u041d\u0410\u0427\u0410\u041b\u0421\u042f!\" );\r\n break;\r\n\r\n case ArenaRoundState.RoundEnd:\r\n StateTimer = EndPauseDuration;\r\n Log.Info( \"[ArenaRoundManager] \u0420\u0430\u0443\u043d\u0434 \u043e\u043a\u043e\u043d\u0447\u0435\u043d!\" );\r\n break;\r\n }\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "ExplosiveBarrel.cs",
"FileName": "ExplosiveBarrel.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\nusing System.Linq;\r\n\r\npublic sealed class ExplosiveBarrel : Component, Component.ICollisionListener\r\n{\r\n [Property] public float ExplosionRadius { get; set; } = 450f;\r\n [Property] public float ExplosionForce { get; set; } = 65000f;\r\n [Property] public float ImpactThreshold { get; set; } = 250f;\r\n [Property] public GameObject ExplosionParticle { get; set; }\r\n [Property] public SoundEvent ExplosionSound { get; set; }\r\n\r\n private bool _hasExploded = false;\r\n\r\n public void Explode()\r\n {\r\n if ( _hasExploded || !GameObject.IsValid() ) return;\r\n _hasExploded = true;\r\n\r\n if ( ExplosionParticle.IsValid() )\r\n {\r\n ExplosionParticle.Clone( WorldPosition );\r\n }\r\n\r\n if ( ExplosionSound != null )\r\n {\r\n Sound.Play( ExplosionSound, WorldPosition );\r\n }\r\n\r\n var hits = Scene.Trace.Sphere( ExplosionRadius, WorldPosition, WorldPosition + Vector3.Up * 2f )\r\n .IgnoreGameObject( GameObject )\r\n .RunAll();\r\n\r\n foreach ( var hit in hits )\r\n {\r\n if ( !hit.GameObject.IsValid() ) continue;\r\n\r\n var targetObj = hit.GameObject.Root;\r\n\r\n var shield = targetObj.Components.Get<ShieldAbility>( true );\r\n if ( shield.IsValid() && shield.IsShieldActive ) continue;\r\n\r\n var chainBarrel = targetObj.Components.Get<ExplosiveBarrel>();\r\n if ( chainBarrel.IsValid() && chainBarrel != this )\r\n {\r\n chainBarrel.Explode();\r\n continue;\r\n }\r\n\r\n var rb = targetObj.Components.Get<Rigidbody>();\r\n if ( rb.IsValid() )\r\n {\r\n var dir = (hit.GameObject.WorldPosition - WorldPosition).Normal;\r\n dir.z = MathF.Max( dir.z, 0.4f );\r\n rb.ApplyImpulse( dir * ExplosionForce );\r\n }\r\n }\r\n\r\n Log.Info( $\"[ExplosiveBarrel] Booom! Barrel exploded at {WorldPosition}\" );\r\n GameObject.Destroy();\r\n }\r\n\r\n public void OnCollisionStart( Collision collision )\r\n {\r\n if ( collision.Contact.Speed.Length > ImpactThreshold )\r\n {\r\n Explode();\r\n }\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "HeavyAnvil.cs",
"FileName": "HeavyAnvil.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic sealed class HeavyAnvil : Component, Component.ICollisionListener\r\n{\r\n [Property] public float PushResistanceMultiplier { get; set; } = 0.25f;\r\n [Property] public float CrushSpeedThreshold { get; set; } = 300f;\r\n [Property] public float CrushImpactForce { get; set; } = 75000f;\r\n [Property] public SoundEvent ImpactSound { get; set; }\r\n\r\n public void OnCollisionStart( Collision collision )\r\n {\r\n if ( collision.Contact.Speed.Length < CrushSpeedThreshold ) return;\r\n\r\n var targetObj = collision.Other.GameObject?.Root;\r\n if ( !targetObj.IsValid() || targetObj == GameObject.Root ) return;\r\n\r\n if ( ImpactSound != null )\r\n {\r\n Sound.Play( ImpactSound, WorldPosition );\r\n }\r\n\r\n var cc = targetObj.Components.Get<CharacterController>();\r\n if ( cc.IsValid() )\r\n {\r\n var dir = (targetObj.WorldPosition - WorldPosition).Normal;\r\n dir.z = 0.5f;\r\n cc.Velocity += dir * 900f;\r\n }\r\n\r\n var rb = targetObj.Components.Get<Rigidbody>();\r\n if ( rb.IsValid() )\r\n {\r\n var dir = (targetObj.WorldPosition - WorldPosition).Normal;\r\n dir.z = 0.5f;\r\n rb.ApplyImpulse( dir * CrushImpactForce );\r\n }\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "Killfeed.cs",
"FileName": "Killfeed.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System.Collections.Generic;\r\n\r\npublic sealed class Killfeed : Component\r\n{\r\n\tpublic static Killfeed Instance { get; private set; }\r\n\r\n\tpublic struct KillEntry\r\n\t{\r\n\t\tpublic string KillerName { get; set; }\r\n\t\tpublic string VictimName { get; set; }\r\n\t\tpublic string Method { get; set; }\r\n\t\tpublic float Time { get; set; }\r\n\t}\r\n\r\n\tpublic List<KillEntry> Entries { get; private set; } = new();\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tInstance = this;\r\n\t}\r\n\r\n\tpublic void AddEntry( string killer, string victim, string method = \"Push\" )\r\n\t{\r\n\t\tEntries.Add( new KillEntry\r\n\t\t{\r\n\t\t\tKillerName = killer,\r\n\t\t\tVictimName = victim,\r\n\t\t\tMethod = method,\r\n\t\t\tTime = RealTime.Now\r\n\t\t} );\r\n\r\n\t\tif ( Entries.Count > 5 )\r\n\t\t{\r\n\t\t\tEntries.RemoveAt( 0 );\r\n\t\t}\r\n\t}\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "ObjectGrabber.cs",
"FileName": "ObjectGrabber.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic sealed class ObjectGrabber : Component\r\n{\r\n\t[Property] public float MaxReachDistance { get; set; } = 300f;\r\n\t[Property] public float HoldDistance { get; set; } = 150f;\r\n\t[Property] public float ThrowForce { get; set; } = 1200f;\r\n\r\n\tprivate GameObject _heldObject;\r\n\tprivate Rigidbody _heldRigidbody;\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( IsProxy ) return;\r\n\r\n\t\tif ( Input.Pressed( \"use\" ) || Input.Pressed( \"e\" ) )\r\n\t\t{\r\n\t\t\tif ( _heldObject.IsValid() )\r\n\t\t\t{\r\n\t\t\t\tDropObject();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tTryGrabObject();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( Input.Pressed( \"attack1\" ) && _heldObject.IsValid() )\r\n\t\t{\r\n\t\t\tThrowObject();\r\n\t\t}\r\n\r\n\t\tif ( _heldObject.IsValid() )\r\n\t\t{\r\n\t\t\tUpdateHeldObjectPosition();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void TryGrabObject()\r\n\t{\r\n\t\tvar cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();\r\n\t\tif ( !cam.IsValid() ) return;\r\n\r\n\t\tvar ray = cam.Transform.World.Forward;\r\n\t\tvar start = cam.WorldPosition;\r\n\t\tvar end = start + ray * MaxReachDistance;\r\n\r\n\t\tvar tr = Scene.Trace.Ray( start, end )\r\n\t\t\t.IgnoreGameObject( GameObject )\r\n\t\t\t.Run();\r\n\r\n\t\tif ( tr.Hit && tr.GameObject.IsValid() )\r\n\t\t{\r\n\t\t\tvar rootObj = tr.GameObject.Root;\r\n\t\t\tvar rb = rootObj.Components.Get<Rigidbody>() ?? tr.GameObject.Components.Get<Rigidbody>();\r\n\r\n\t\t\tif ( rb.IsValid() && !rootObj.Tags.Has( \"player\" ) )\r\n\t\t\t{\r\n\t\t\t\t_heldObject = rootObj;\r\n\t\t\t\t_heldRigidbody = rb;\r\n\t\t\t\t_heldRigidbody.MotionEnabled = true;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void UpdateHeldObjectPosition()\r\n\t{\r\n\t\tvar cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();\r\n\t\tif ( !cam.IsValid() ) return;\r\n\r\n\t\tvar targetPos = cam.WorldPosition + cam.Transform.World.Forward * HoldDistance;\r\n\t\tvar dir = targetPos - _heldObject.WorldPosition;\r\n\r\n\t\tif ( _heldRigidbody.IsValid() )\r\n\t\t{\r\n\t\t\t_heldRigidbody.Velocity = dir * 10f;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_heldObject.WorldPosition = Vector3.Lerp( _heldObject.WorldPosition, targetPos, Time.Delta * 15f );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void DropObject()\r\n\t{\r\n\t\t_heldObject = null;\r\n\t\t_heldRigidbody = null;\r\n\t}\r\n\r\n\tprivate void ThrowObject()\r\n\t{\r\n\t\tif ( !_heldObject.IsValid() ) return;\r\n\r\n\t\tvar cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();\r\n\t\tvar throwDir = cam.IsValid() ? cam.Transform.World.Forward : Transform.World.Forward;\r\n\r\n\t\tif ( _heldRigidbody.IsValid() )\r\n\t\t{\r\n\t\t\t_heldRigidbody.Velocity = throwDir * ThrowForce;\r\n\t\t}\r\n\r\n\t\tCameraJuice.Instance?.Shake( 8f );\r\n\r\n\t\t_heldObject = null;\r\n\t\t_heldRigidbody = null;\r\n\t}\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "Assembly.cs",
"FileName": "Assembly.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "global using Sandbox;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\n"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "MapEventManager.cs",
"FileName": "MapEventManager.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\nusing System.Linq;\r\n\r\npublic enum WeatherType\r\n{\r\n None,\r\n LowGravity,\r\n SlipperyIce,\r\n StrongWind\r\n}\r\n\r\npublic sealed class MapEventManager : Component\r\n{\r\n public static MapEventManager Instance { get; private set; }\r\n\r\n [Property] public float EventDuration { get; set; } = 12f;\r\n [Property] public float EventInterval { get; set; } = 25f;\r\n [Property] public float LowGravityForce { get; set; } = 450f;\r\n [Property] public float WindForce { get; set; } = 400f;\r\n\r\n public WeatherType CurrentEvent { get; private set; } = WeatherType.None;\r\n public Vector3 CurrentWindDirection { get; private set; } = Vector3.Right;\r\n public float TimeRemaining { get; private set; }\r\n\r\n private TimeUntil _nextEventTime;\r\n private TimeUntil _eventEndTime;\r\n\r\n protected override void OnAwake()\r\n {\r\n Instance = this;\r\n _nextEventTime = EventInterval;\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n if ( IsProxy ) return;\r\n\r\n if ( CurrentEvent == WeatherType.None && _nextEventTime )\r\n {\r\n StartRandomEvent();\r\n }\r\n else if ( CurrentEvent != WeatherType.None )\r\n {\r\n TimeRemaining = _eventEndTime;\r\n if ( _eventEndTime )\r\n {\r\n EndEvent();\r\n }\r\n }\r\n }\r\n\r\n protected override void OnFixedUpdate()\r\n {\r\n if ( CurrentEvent == WeatherType.None ) return;\r\n\r\n var players = Scene.GetAllComponents<CharacterController>();\r\n var rigidbodies = Scene.GetAllComponents<Rigidbody>();\r\n\r\n if ( CurrentEvent == WeatherType.LowGravity )\r\n {\r\n foreach ( var cc in players )\r\n {\r\n if ( cc.IsValid() )\r\n {\r\n cc.Velocity += Vector3.Up * LowGravityForce * Time.Delta;\r\n }\r\n }\r\n foreach ( var rb in rigidbodies )\r\n {\r\n if ( rb.IsValid() && !rb.IsProxy )\r\n {\r\n rb.ApplyImpulse( Vector3.Up * (rb.PhysicsBody.Mass * LowGravityForce * Time.Delta) );\r\n }\r\n }\r\n }\r\n else if ( CurrentEvent == WeatherType.StrongWind )\r\n {\r\n foreach ( var cc in players )\r\n {\r\n if ( cc.IsValid() )\r\n {\r\n cc.Velocity += CurrentWindDirection * WindForce * Time.Delta;\r\n }\r\n }\r\n foreach ( var rb in rigidbodies )\r\n {\r\n if ( rb.IsValid() && !rb.IsProxy )\r\n {\r\n rb.ApplyImpulse( CurrentWindDirection * WindForce * 15f * Time.Delta );\r\n }\r\n }\r\n }\r\n else if ( CurrentEvent == WeatherType.SlipperyIce )\r\n {\r\n foreach ( var cc in players )\r\n {\r\n if ( cc.IsValid() && cc.IsOnGround )\r\n {\r\n var vel = cc.Velocity;\r\n vel.x *= 1.015f;\r\n vel.y *= 1.015f;\r\n cc.Velocity = vel;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private void StartRandomEvent()\r\n {\r\n var values = Enum.GetValues( typeof( WeatherType ) ).Cast<WeatherType>().Where( w => w != WeatherType.None ).ToArray();\r\n CurrentEvent = values[Random.Shared.Next( values.Length )];\r\n _eventEndTime = EventDuration;\r\n\r\n if ( CurrentEvent == WeatherType.StrongWind )\r\n {\r\n float angle = Random.Shared.NextSingle() * MathF.PI * 2f;\r\n CurrentWindDirection = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f ).Normal;\r\n }\r\n\r\n Log.Info( $\"[MapEventManager] \u0417\u0430\u043f\u0443\u0449\u0435\u043d \u043f\u043e\u0433\u043e\u0434\u043d\u044b\u0439 \u0438\u0432\u0435\u043d\u0442: {CurrentEvent}\" );\r\n }\r\n\r\n private void EndEvent()\r\n {\r\n Log.Info( $\"[MapEventManager] \u0417\u0430\u0432\u0435\u0440\u0448\u0451\u043d \u0438\u0432\u0435\u043d\u0442: {CurrentEvent}\" );\r\n CurrentEvent = WeatherType.None;\r\n _nextEventTime = EventInterval;\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "PlayerAbilities.cs",
"FileName": "PlayerAbilities.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\n\r\npublic sealed class PlayerAbilities : Component\r\n{\r\n [Property] public GameObject HeavyObjectPrefab { get; set; }\r\n\r\n [Property] public float DropCooldown { get; set; } = 8f;\r\n [Property] public float ShieldCooldown { get; set; } = 6f;\r\n [Property] public float ShieldDuration { get; set; } = 2f;\r\n\r\n public bool IsShieldActive { get; private set; } = false;\r\n\r\n private TimeSince _timeSinceDrop = 100f;\r\n private TimeSince _timeSinceShield = 100f;\r\n private TimeSince _shieldActiveTimer = 0f;\r\n\r\n private Rigidbody _rb;\r\n private float _originalMass = 0f;\r\n\r\n protected override void OnStart()\r\n {\r\n _rb = Components.Get<Rigidbody>();\r\n if ( _rb.IsValid() )\r\n {\r\n _originalMass = _rb.MassOverride;\r\n }\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n if ( IsProxy ) return;\r\n\r\n if ( IsShieldActive && _shieldActiveTimer > ShieldDuration )\r\n {\r\n DeactivateShield();\r\n }\r\n\r\n if ( Input.Pressed( \"attack2\" ) || Input.Pressed( \"use\" ) )\r\n {\r\n TryDropObject();\r\n }\r\n\r\n if ( Input.Pressed( \"run\" ) )\r\n {\r\n TryActivateShield();\r\n }\r\n }\r\n\r\n private void TryDropObject()\r\n {\r\n if ( _timeSinceDrop < DropCooldown )\r\n {\r\n Log.Info( $\"[\u0421\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438] \u0421\u0431\u0440\u043e\u0441 \u043d\u0430 \u043f\u0435\u0440\u0435\u0437\u0430\u0440\u044f\u0434\u043a\u0435! \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c: {DropCooldown - _timeSinceDrop:F1} \u0441\u0435\u043a.\" );\r\n return;\r\n }\r\n\r\n var headPos = WorldPosition + Vector3.Up * 50f;\r\n var forwardPos = headPos + WorldTransform.Forward * 600f;\r\n\r\n var tr = Scene.Trace.Ray( headPos, forwardPos )\r\n .IgnoreGameObjectHierarchy( GameObject )\r\n .Run();\r\n\r\n if ( tr.Hit && tr.GameObject.IsValid() )\r\n {\r\n var targetPos = tr.GameObject.WorldPosition;\r\n var spawnPos = targetPos + Vector3.Up * 180f;\r\n\r\n if ( HeavyObjectPrefab.IsValid() )\r\n {\r\n var obj = HeavyObjectPrefab.Clone( spawnPos );\r\n if ( obj.IsValid() && Network.Active )\r\n {\r\n obj.NetworkSpawn();\r\n }\r\n\r\n _timeSinceDrop = 0;\r\n Log.Info( \"[\u0421\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438] \u0422\u044f\u0436\u0435\u043b\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u0431\u0440\u043e\u0448\u0435\u043d \u043f\u0440\u044f\u043c\u043e \u043d\u0430 \u0432\u0440\u0430\u0433\u0430!\" );\r\n }\r\n else\r\n {\r\n Log.Warning( \"[\u0421\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438] HeavyObjectPrefab \u043d\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d \u0432 \u0418\u043d\u0441\u043f\u0435\u043a\u0442\u043e\u0440\u0435!\" );\r\n }\r\n }\r\n else\r\n {\r\n Log.Info( \"[\u0421\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438] \u041d\u0438\u043a\u043e\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 \u043f\u0435\u0440\u0435\u0434 \u0441\u043e\u0431\u043e\u0439 \u0434\u043b\u044f \u0430\u0442\u0430\u043a\u0438!\" );\r\n }\r\n }\r\n\r\n private void TryActivateShield()\r\n {\r\n if ( _timeSinceShield < ShieldCooldown || IsShieldActive ) return;\r\n\r\n IsShieldActive = true;\r\n _shieldActiveTimer = 0;\r\n _timeSinceShield = 0;\r\n\r\n if ( _rb.IsValid() )\r\n {\r\n _originalMass = _rb.MassOverride;\r\n _rb.MassOverride = 1000f; \r\n }\r\n\r\n Log.Info( \"[\u0421\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438] \u0429\u0418\u0422 \u0410\u041a\u0422\u0418\u0412\u0418\u0420\u041e\u0412\u0410\u041d!\" );\r\n }\r\n\r\n private void DeactivateShield()\r\n {\r\n IsShieldActive = false;\r\n\r\n if ( _rb.IsValid() )\r\n {\r\n _rb.MassOverride = _originalMass; \r\n }\r\n\r\n Log.Info( \"[\u0421\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438] \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0449\u0438\u0442\u0430 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u043e\u0441\u044c.\" );\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "LeaderboardModal.razor",
"FileName": "LeaderboardModal.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "@using Sandbox;\r\n@using Sandbox.UI;\r\n@using Sandbox.Services;\r\n@using System;\r\n@using System.Threading.Tasks;\r\n@using System.Collections.Generic;\r\n@using System.Linq;\r\n@inherits Panel\r\n\r\n<root class=\"lb-root @(IsOpen ? \"open\" : \"\")\">\r\n <div class=\"lb-card\">\r\n <div class=\"lb-header\">\r\n <div class=\"lb-title\">GLOBAL LEADERBOARD</div>\r\n <button class=\"lb-close\" onclick=\"@Toggle\">\u2715</button>\r\n </div>\r\n\r\n <div class=\"lb-tabs\">\r\n <button class=\"tab-btn @(CurrentStat == \"pushes_count\" ? \"active\" : \"\")\" onclick=\"@(() => SelectStat(\"pushes_count\"))\">\r\n PUSHES\r\n </button>\r\n <button class=\"tab-btn @(CurrentStat == \"rounds_won\" ? \"active\" : \"\")\" onclick=\"@(() => SelectStat(\"rounds_won\"))\">\r\n WINS\r\n </button>\r\n </div>\r\n\r\n <div class=\"lb-content\">\r\n @if (IsLoading)\r\n {\r\n <div class=\"lb-status\">LOADING DATA...</div>\r\n }\r\n else if (Entries == null || Entries.Count == 0)\r\n {\r\n <div class=\"lb-status\">NO RECORD FOUND</div>\r\n }\r\n else\r\n {\r\n <div class=\"lb-table\">\r\n <div class=\"table-header\">\r\n <span class=\"col-rank\">#</span>\r\n <span class=\"col-player\">PLAYER</span>\r\n <span class=\"col-score\">SCORE</span>\r\n </div>\r\n <div class=\"table-body\">\r\n @foreach (var entry in Entries)\r\n {\r\n <div class=\"table-row @(entry.SteamId == (Connection.Local?.SteamId ?? 0) ? \"is-me\" : \"\")\">\r\n <span class=\"col-rank\">@GetRankBadge(entry.Rank)</span>\r\n <span class=\"col-player\">@entry.DisplayName</span>\r\n <span class=\"col-score\">@entry.Value.ToString(\"N0\")</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</root>\r\n\r\n@code {\r\n public class EntryItem\r\n {\r\n public int Rank { get; set; }\r\n public string DisplayName { get; set; }\r\n public double Value { get; set; }\r\n public ulong SteamId { get; set; }\r\n }\r\n\r\n public bool IsOpen { get; set; } = false;\r\n public bool IsLoading { get; set; } = true;\r\n public string CurrentStat { get; set; } = \"pushes_count\";\r\n public List<EntryItem> Entries { get; set; } = new();\r\n\r\n protected override void OnAfterTreeRender( bool firstTime )\r\n {\r\n base.OnAfterTreeRender( firstTime );\r\n if ( firstTime )\r\n {\r\n _ = RefreshData();\r\n }\r\n }\r\n\r\n public void Toggle()\r\n {\r\n IsOpen = !IsOpen;\r\n if (IsOpen)\r\n {\r\n _ = RefreshData();\r\n }\r\n StateHasChanged();\r\n }\r\n\r\n private async Task SelectStat(string statIdent)\r\n {\r\n if (CurrentStat == statIdent) return;\r\n CurrentStat = statIdent;\r\n await RefreshData();\r\n }\r\n\r\n private async Task RefreshData()\r\n {\r\n IsLoading = true;\r\n StateHasChanged();\r\n\r\n try\r\n {\r\n var board = Leaderboards.GetFromStat(\"seilordstudio.one_more_push\", CurrentStat);\r\n board.SetAggregationSum();\r\n board.MaxEntries = 20;\r\n await board.Refresh();\r\n\r\n if (board.Entries != null)\r\n {\r\n Entries = board.Entries.Select(x => new EntryItem\r\n {\r\n Rank = Convert.ToInt32(x.Rank),\r\n DisplayName = x.DisplayName ?? \"Player\",\r\n Value = Convert.ToDouble(x.Value),\r\n SteamId = Convert.ToUInt64(x.SteamId)\r\n }).ToList();\r\n }\r\n else\r\n {\r\n Entries = new();\r\n }\r\n }\r\n catch (System.Exception ex)\r\n {\r\n Log.Warning($\"Leaderboard fetch failed: {ex.Message}\");\r\n }\r\n\r\n IsLoading = false;\r\n StateHasChanged();\r\n }\r\n\r\n private string GetRankBadge(int rank)\r\n {\r\n return rank switch\r\n {\r\n 1 => \"\ud83e\udd47 1\",\r\n 2 => \"\ud83e\udd48 2\",\r\n 3 => \"\ud83e\udd49 3\",\r\n _ => rank.ToString()\r\n };\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "RoundManager.cs",
"FileName": "RoundManager.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\n\r\npublic sealed class RoundManager : Component\r\n{\r\n public static RoundManager Instance { get; private set; }\r\n\r\n [Property] public float RoundTime { get; set; } = 120f;\r\n [Sync] public float TimeLeft { get; set; }\r\n [Sync] public bool IsInRound { get; set; }\r\n [Sync] public int CurrentRound { get; set; } = 1;\r\n\r\n protected override void OnAwake()\r\n {\r\n Instance = this;\r\n }\r\n\r\n protected override void OnStart()\r\n {\r\n StartNewRound();\r\n }\r\n\r\n public void StartNewRound()\r\n {\r\n TimeLeft = RoundTime;\r\n IsInRound = true;\r\n\r\n ResetArena();\r\n RespawnAllPlayers();\r\n\r\n var shrinker = Scene.GetAllComponents<ArenaShrinker>().FirstOrDefault();\r\n if ( shrinker.IsValid() )\r\n {\r\n shrinker.StartRound();\r\n }\r\n\r\n Log.Info( $\"[RoundManager] \u0420\u0430\u0443\u043d\u0434 {CurrentRound} \u043d\u0430\u0447\u0430\u0442!\" );\r\n }\r\n\r\n public void OnPlayerEliminated( GameObject player )\r\n {\r\n if ( !IsInRound ) return;\r\n\r\n if ( player.IsValid() )\r\n {\r\n player.Enabled = false;\r\n }\r\n\r\n var activePlayers = Scene.GetAllComponents<PushAbility>()\r\n .Where( p => p.GameObject.IsValid() && p.GameObject.Enabled );\r\n\r\n if ( activePlayers.Count() <= 1 )\r\n {\r\n _ = EndRound();\r\n }\r\n }\r\n\r\n public async Task EndRound()\r\n {\r\n if ( !IsInRound ) return;\r\n\r\n var winner = Scene.GetAllComponents<PushAbility>()\r\n .FirstOrDefault( p => p.GameObject.IsValid() && p.GameObject.Enabled );\r\n\r\n if ( winner.IsValid() && !winner.IsProxy )\r\n {\r\n GameServices.RecordRoundWin();\r\n }\r\n\r\n _ = GameServices.SaveStatsAsync();\r\n\r\n IsInRound = false;\r\n CurrentRound++;\r\n\r\n Log.Info( $\"\u0420\u0430\u0443\u043d\u0434 {CurrentRound - 1} \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d! \u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0447\u0435\u0440\u0435\u0437 3 \u0441\u0435\u043a\u0443\u043d\u0434\u044b...\" );\r\n\r\n await Task.DelaySeconds( 3f );\r\n\r\n StartNewRound();\r\n }\r\n\r\n private void ResetArena()\r\n {\r\n var tiles = Scene.GetAllComponents<ArenaTile>();\r\n foreach ( var tile in tiles )\r\n {\r\n if ( tile.IsValid() )\r\n {\r\n tile.ResetTile();\r\n }\r\n }\r\n }\r\n\r\n private void RespawnAllPlayers()\r\n {\r\n var spawn = Scene.GetAllComponents<SpawnPoint>().FirstOrDefault();\r\n Vector3 spawnPos = spawn.IsValid() ? spawn.WorldPosition : new Vector3( 0, 0, 250 );\r\n\r\n var players = Scene.GetAllComponents<PushAbility>();\r\n foreach ( var player in players )\r\n {\r\n if ( !player.IsValid() ) continue;\r\n\r\n var root = player.GameObject.Root;\r\n root.Enabled = true;\r\n root.WorldPosition = spawnPos;\r\n\r\n var cc = root.Components.Get<CharacterController>( true );\r\n if ( cc.IsValid() )\r\n {\r\n cc.Velocity = Vector3.Zero;\r\n }\r\n\r\n var rb = root.Components.Get<Rigidbody>( true );\r\n if ( rb.IsValid() )\r\n {\r\n rb.Velocity = Vector3.Zero;\r\n rb.AngularVelocity = Vector3.Zero;\r\n }\r\n }\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "ShopManager.cs",
"FileName": "ShopManager.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\n\r\npublic class PushItem\r\n{\r\n public string Id { get; set; }\r\n public string NameRu { get; set; }\r\n public string NameEn { get; set; }\r\n public int Cost { get; set; }\r\n public float ForceMultiplier { get; set; }\r\n public string Key { get; set; }\r\n public bool IsUnlocked { get; set; }\r\n\r\n public string GetName( string lang ) => lang == \"EN\" ? NameEn : NameRu;\r\n}\r\n\r\npublic class ShopManager : Component\r\n{\r\n public static ShopManager Instance { get; private set; }\r\n\r\n public List<PushItem> Items { get; private set; } = new()\r\n {\r\n new PushItem { Id = \"basic\", NameRu = \"\u041e\u0431\u044b\u0447\u043d\u044b\u0439\", NameEn = \"Basic\", Cost = 0, ForceMultiplier = 1.0f, Key = \"1\", IsUnlocked = true },\r\n new PushItem { Id = \"heavy\", NameRu = \"\u0422\u044f\u0436\u0435\u043b\u044b\u0439\", NameEn = \"Heavy\", Cost = 10, ForceMultiplier = 1.5f, Key = \"2\", IsUnlocked = false },\r\n new PushItem { Id = \"force\", NameRu = \"\u0418\u043c\u043f\u0443\u043b\u044c\u0441\", NameEn = \"Impulse\", Cost = 25, ForceMultiplier = 2.2f, Key = \"3\", IsUnlocked = false },\r\n new PushItem { Id = \"mega\", NameRu = \"\u041c\u0435\u0433\u0430-\u0442\u043e\u043b\u0447\u043e\u043a\", NameEn = \"Mega Push\", Cost = 50, ForceMultiplier = 3.5f, Key = \"4\", IsUnlocked = false }\r\n };\r\n\r\n public PushItem SelectedItem { get; private set; }\r\n\r\n protected override void OnStart()\r\n {\r\n Instance = this;\r\n SelectedItem = Items.FirstOrDefault( x => x.IsUnlocked );\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n if ( Input.Pressed( \"slot1\" ) ) SelectByKey( \"1\" );\r\n if ( Input.Pressed( \"slot2\" ) ) SelectByKey( \"2\" );\r\n if ( Input.Pressed( \"slot3\" ) ) SelectByKey( \"3\" );\r\n if ( Input.Pressed( \"slot4\" ) ) SelectByKey( \"4\" );\r\n }\r\n\r\n public void SelectByKey( string key )\r\n {\r\n var item = Items.FirstOrDefault( x => x.Key == key );\r\n if ( item != null ) TryBuyOrSelect( item );\r\n }\r\n\r\n public bool TryBuyOrSelect( PushItem item )\r\n {\r\n if ( item.IsUnlocked )\r\n {\r\n SelectedItem = item;\r\n return true;\r\n }\r\n\r\n int currentScore = ScoreManager.Instance?.Score ?? 0;\r\n if ( currentScore >= item.Cost )\r\n {\r\n item.IsUnlocked = true;\r\n SelectedItem = item;\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n public float GetCurrentForceMultiplier()\r\n {\r\n return SelectedItem?.ForceMultiplier ?? 1.0f;\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "MainMenu.razor",
"FileName": "MainMenu.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "@namespace Sandbox\r\n@using Sandbox;\r\n@using Sandbox.UI;\r\n@inherits PanelComponent\r\n\r\n<root>\r\n <div class=\"background-image\"></div>\r\n <div class=\"background-overlay\"></div>\r\n\r\n <div class=\"header\">\r\n <div class=\"logo\">\r\n <span class=\"logo-text\">@GetText(\"game_title\")</span>\r\n </div>\r\n <div class=\"header-actions\">\r\n <button class=\"icon-button\" onclick=\"@OnSettingsClicked\" onmouseover=\"@PlayHoverSound\">\r\n <span>\u2699\ufe0f\ufe0f @GetText(\"settings_btn\")</span>\r\n </button>\r\n <button class=\"icon-button lang\" onclick=\"@OnLangClicked\" onmouseover=\"@PlayHoverSound\">\r\n <span>\ud83c\udf10 @GetText(\"lang_btn\")</span>\r\n </button>\r\n <button class=\"icon-button exit-btn\" onclick=\"@OnQuitClicked\" onmouseover=\"@PlayHoverSound\">\r\n <span>\ud83d\udeaa @GetText(\"exit_btn\")</span>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"main-content\">\r\n <div class=\"modes-section\">\r\n <div class=\"title-group\">\r\n <div class=\"main-title\">@GetText(\"title\")</div>\r\n <div class=\"subtitle\">@GetText(\"subtitle\")</div>\r\n </div>\r\n\r\n <div class=\"modes-wrapper\">\r\n <div class=\"mode-card sumo\" onclick=\"@(() => OnModeClicked(GameMode.SumoPush))\" onmouseover=\"@PlayHoverSound\">\r\n <div class=\"card-badge sumo-badge\">@GetText(\"sumo_tag\")</div>\r\n <div class=\"card-icon-wrapper\">\r\n <span class=\"card-icon\">\ud83e\udd3c</span>\r\n </div>\r\n <div class=\"card-content\">\r\n <div class=\"mode-title\">@GetText(\"sumo_title\")</div>\r\n <div class=\"mode-desc\">@GetText(\"sumo_desc\")</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"mode-card nextbot disabled\">\r\n <div class=\"card-badge nextbot-badge disabled-badge\">@GetText(\"nextbot_tag\")</div>\r\n <div class=\"card-icon-wrapper\">\r\n <span class=\"card-icon\">\ud83c\udfc3</span>\r\n </div>\r\n <div class=\"card-content\">\r\n <div class=\"mode-title\">@GetText(\"nextbot_title\")</div>\r\n <div class=\"mode-desc\">@GetText(\"nextbot_desc\")</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"news-panel\">\r\n <div class=\"news-header\">\r\n <span class=\"news-title\">@GetText(\"news_title\")</span>\r\n <span class=\"news-badge\">@GetText(\"news_tag_new\")</span>\r\n </div>\r\n <div class=\"news-list\">\r\n <div class=\"news-item item-highlight\" onmouseover=\"@PlayHoverSound\">\r\n <div class=\"news-item-title\">\ud83d\udd25 @GetText(\"news_item1_title\")</div>\r\n <div class=\"news-item-desc\">@GetText(\"news_item1_desc\")</div>\r\n </div>\r\n <div class=\"news-item\" onmouseover=\"@PlayHoverSound\">\r\n <div class=\"news-item-title\">\u2728 @GetText(\"news_item2_title\")</div>\r\n <div class=\"news-item-desc\">@GetText(\"news_item2_desc\")</div>\r\n </div>\r\n <div class=\"news-item\" onmouseover=\"@PlayHoverSound\">\r\n <div class=\"news-item-title\">\u26a1 @GetText(\"news_item3_title\")</div>\r\n <div class=\"news-item-desc\">@GetText(\"news_item3_desc\")</div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if ( IsSettingsOpen )\r\n {\r\n <div class=\"modal-overlay\">\r\n <div class=\"modal-window\">\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">@GetText(\"settings_title\")</div>\r\n <button class=\"close-x\" onclick=\"@OnCloseSettingsClicked\" onmouseover=\"@PlayHoverSound\">\u2715</button>\r\n </div>\r\n\r\n <div class=\"modal-body\">\r\n <div class=\"setting-item\">\r\n <div class=\"setting-label\">@GetText(\"volume_label\"): @((int)(Volume * 100))%</div>\r\n <div class=\"volume-buttons\">\r\n <button class=\"vol-btn\" onclick=\"@(() => OnVolumeBtnClicked(0.25f))\" onmouseover=\"@PlayHoverSound\">25%</button>\r\n <button class=\"vol-btn\" onclick=\"@(() => OnVolumeBtnClicked(0.50f))\" onmouseover=\"@PlayHoverSound\">50%</button>\r\n <button class=\"vol-btn\" onclick=\"@(() => OnVolumeBtnClicked(0.75f))\" onmouseover=\"@PlayHoverSound\">75%</button>\r\n <button class=\"vol-btn\" onclick=\"@(() => OnVolumeBtnClicked(1.00f))\" onmouseover=\"@PlayHoverSound\">100%</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"controls-section\">\r\n <div class=\"controls-title\">@GetText(\"controls_title\")</div>\r\n <div class=\"control-row\">@GetText(\"ctrl_move\")</div>\r\n <div class=\"control-row\">@GetText(\"ctrl_action\")</div>\r\n <div class=\"control-row\">@GetText(\"ctrl_space\")</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button class=\"modal-close-btn\" onclick=\"@OnCloseSettingsClicked\" onmouseover=\"@PlayHoverSound\">@GetText(\"close_btn\")</button>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if ( IsLoading )\r\n {\r\n <div class=\"loading-overlay\">\r\n <div class=\"loading-box\">\r\n <div class=\"loading-mode-title\">@(TargetMode == GameMode.SumoPush ? GetText(\"sumo_title\") : GetText(\"nextbot_title\"))</div>\r\n <div class=\"loading-status\">@GetText(\"loading_title\")</div>\r\n <div class=\"loading-sub\">@GetText(\"loading_sub\")</div>\r\n\r\n <div class=\"progress-bar-bg\">\r\n <div class=\"progress-bar-fill\" style=\"width: @(LoadingProgress)%;\"></div>\r\n </div>\r\n <div class=\"loading-percent\">@((int)LoadingProgress)%</div>\r\n\r\n <div class=\"tip-box\">\r\n <span class=\"tip-label\">@GetText(\"tip_label\")</span>\r\n <span class=\"tip-text\">@(TargetMode == GameMode.SumoPush ? GetText(\"tip_sumo\") : GetText(\"tip_nextbot\"))</span>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</root>\r\n\r\n@code {\r\n [Property] public SoundEvent HoverSound { get; set; }\r\n [Property] public SoundEvent ClickSound { get; set; }\r\n\r\n private bool IsSettingsOpen { get; set; } = false;\r\n private bool IsLoading { get; set; } = false;\r\n private GameMode TargetMode { get; set; }\r\n private float LoadingProgress { get; set; } = 0f;\r\n private TimeSince TimeSinceLoadingStarted;\r\n private float Volume { get; set; } = 1.0f;\r\n private const float TotalLoadingTime = 6.0f;\r\n\r\n private void PlayHoverSound()\r\n {\r\n if ( HoverSound != null )\r\n Sound.Play( HoverSound );\r\n else\r\n Sound.Play( \"ui.button.hover\" );\r\n }\r\n\r\n private void PlayClickSound()\r\n {\r\n if ( ClickSound != null )\r\n Sound.Play( ClickSound );\r\n else\r\n Sound.Play( \"ui.button.press\" );\r\n }\r\n\r\n protected override void OnUpdate()\r\n {\r\n base.OnUpdate();\r\n\r\n if ( IsLoading )\r\n {\r\n float elapsed = TimeSinceLoadingStarted;\r\n LoadingProgress = MathX.Clamp( (elapsed / TotalLoadingTime) * 100f, 0f, 100f );\r\n\r\n if ( elapsed >= TotalLoadingTime )\r\n {\r\n IsLoading = false;\r\n if ( ModeController.Instance != null )\r\n {\r\n ModeController.Instance.SelectMode( TargetMode );\r\n }\r\n }\r\n }\r\n }\r\n\r\n private void OnModeClicked( GameMode mode )\r\n {\r\n PlayClickSound();\r\n StartLoading( mode );\r\n }\r\n\r\n private void OnSettingsClicked()\r\n {\r\n PlayClickSound();\r\n ToggleSettings();\r\n }\r\n\r\n private void OnCloseSettingsClicked()\r\n {\r\n PlayClickSound();\r\n ToggleSettings();\r\n }\r\n\r\n private void OnLangClicked()\r\n {\r\n PlayClickSound();\r\n OnToggleLanguage();\r\n }\r\n\r\n private void OnQuitClicked()\r\n {\r\n PlayClickSound();\r\n Game.Close();\r\n }\r\n\r\n private void OnVolumeBtnClicked( float val )\r\n {\r\n PlayClickSound();\r\n SetVolume( val );\r\n }\r\n\r\n private void StartLoading( GameMode mode )\r\n {\r\n if ( IsLoading ) return;\r\n\r\n TargetMode = mode;\r\n IsLoading = true;\r\n LoadingProgress = 0f;\r\n TimeSinceLoadingStarted = 0;\r\n }\r\n\r\n private void ToggleSettings()\r\n {\r\n if ( IsLoading ) return;\r\n IsSettingsOpen = !IsSettingsOpen;\r\n }\r\n\r\n private void SetVolume( float val )\r\n {\r\n Volume = val;\r\n if ( ModeController.Instance != null )\r\n {\r\n ModeController.Instance.MasterVolume = val;\r\n }\r\n }\r\n\r\n private void OnToggleLanguage()\r\n {\r\n if ( IsLoading ) return;\r\n if ( ModeController.Instance != null )\r\n {\r\n ModeController.Instance.ToggleLanguage();\r\n }\r\n }\r\n\r\n private string GetText( string key )\r\n {\r\n return ModeController.Instance?.GetText( key ) ?? key;\r\n }\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "MetaProgression.cs",
"FileName": "MetaProgression.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\nusing System;\r\n\r\npublic sealed class MetaProgression : Component\r\n{\r\n\t[Property] public int Credits { get; private set; } = 0;\r\n\r\n\t// \u0423\u0440\u043e\u0432\u043d\u0438 \u043c\u0435\u0442\u0430-\u043f\u0440\u043e\u043a\u0430\u0447\u043a\u0438\r\n\t[Property] public int ForceLevel { get; private set; } = 0;\r\n\t[Property] public int MassLevel { get; private set; } = 0;\r\n\t[Property] public int EnergyLevel { get; private set; } = 0;\r\n\r\n\t// \u041f\u0440\u0435\u0441\u0442\u0438\u0436\r\n\t[Property] public int PrestigeLevel { get; private set; } = 0;\r\n\t[Property] public int PrestigeTokens { get; private set; } = 0;\r\n\r\n\t// \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u0431\u0440\u043e\u0441\u0430 (\u0441\u0443\u043c\u043c\u0430 \u0443\u0440\u043e\u0432\u043d\u0435\u0439 \u043f\u0440\u043e\u043a\u0430\u0447\u043a\u0438)\r\n\tpublic const int REQUIRED_TOTAL_LEVELS_FOR_PRESTIGE = 15;\r\n\r\n\tprivate const string SAVE_FILE = \"one_more_push_meta.json\";\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tLoadProgress();\r\n\t}\r\n\r\n\tpublic void AddCredits( int amount )\r\n\t{\r\n\t\t// \u041c\u043d\u043e\u0436\u0438\u0442\u0435\u043b\u044c \u043f\u0440\u0435\u0441\u0442\u0438\u0436\u0430 (+50% \u0437\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043f\u0440\u0435\u0441\u0442\u0438\u0436\u0430)\r\n\t\tfloat multiplier = 1.0f + (PrestigeLevel * 0.5f);\r\n\t\tCredits += (int)(amount * multiplier);\r\n\t\tSaveProgress();\r\n\t}\r\n\r\n\tpublic int GetUpgradeCost( int currentLevel )\r\n\t{\r\n\t\treturn (int)(100 * Math.Pow( 1.5, currentLevel ));\r\n\t}\r\n\r\n\tpublic bool TryUpgradeForce()\r\n\t{\r\n\t\tint cost = GetUpgradeCost( ForceLevel );\r\n\t\tif ( Credits >= cost )\r\n\t\t{\r\n\t\t\tCredits -= cost;\r\n\t\t\tForceLevel++;\r\n\t\t\tSaveProgress();\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tpublic bool TryUpgradeMass()\r\n\t{\r\n\t\tint cost = GetUpgradeCost( MassLevel );\r\n\t\tif ( Credits >= cost )\r\n\t\t{\r\n\t\t\tCredits -= cost;\r\n\t\t\tMassLevel++;\r\n\t\t\tSaveProgress();\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tpublic bool TryUpgradeEnergy()\r\n\t{\r\n\t\tint cost = GetUpgradeCost( EnergyLevel );\r\n\t\tif ( Credits >= cost )\r\n\t\t{\r\n\t\t\tCredits -= cost;\r\n\t\t\tEnergyLevel++;\r\n\t\t\tSaveProgress();\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\t// \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430: \u0433\u043e\u0442\u043e\u0432 \u043b\u0438 \u0438\u0433\u0440\u043e\u043a \u043a \u043f\u0440\u0435\u0441\u0442\u0438\u0436\u0443\r\n\tpublic bool CanPrestige()\r\n\t{\r\n\t\tint totalLevels = ForceLevel + MassLevel + EnergyLevel;\r\n\t\treturn totalLevels >= REQUIRED_TOTAL_LEVELS_FOR_PRESTIGE;\r\n\t}\r\n\r\n\t// \u0421\u0431\u0440\u043e\u0441 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430 \u0440\u0430\u0434\u0438 \u041f\u0440\u0435\u0441\u0442\u0438\u0436\u0430\r\n\tpublic bool TryPrestige()\r\n\t{\r\n\t\tif ( !CanPrestige() ) return false;\r\n\r\n\t\t// \u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0443\u0440\u043e\u0432\u043d\u0438 \u0438 \u043a\u0440\u0435\u0434\u0438\u0442\u044b\r\n\t\tForceLevel = 0;\r\n\t\tMassLevel = 0;\r\n\t\tEnergyLevel = 0;\r\n\t\tCredits = 0;\r\n\r\n\t\t// \u041f\u043e\u0432\u044b\u0448\u0430\u0435\u043c \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043f\u0440\u0435\u0441\u0442\u0438\u0436\u0430 \u0438 \u0432\u044b\u0434\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d\u044b\r\n\t\tPrestigeLevel++;\r\n\t\tPrestigeTokens += 1;\r\n\r\n\t\tSaveProgress();\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic void SaveProgress()\r\n\t{\r\n\t\tvar data = new ProgressData\r\n\t\t{\r\n\t\t\tCredits = this.Credits,\r\n\t\t\tForceLevel = this.ForceLevel,\r\n\t\t\tMassLevel = this.MassLevel,\r\n\t\t\tEnergyLevel = this.EnergyLevel,\r\n\t\t\tPrestigeLevel = this.PrestigeLevel,\r\n\t\t\tPrestigeTokens = this.PrestigeTokens\r\n\t\t};\r\n\r\n\t\tFileSystem.Data.WriteJson( SAVE_FILE, data );\r\n\t}\r\n\r\n\tpublic void LoadProgress()\r\n\t{\r\n\t\tif ( FileSystem.Data.FileExists( SAVE_FILE ) )\r\n\t\t{\r\n\t\t\tvar data = FileSystem.Data.ReadJson<ProgressData>( SAVE_FILE );\r\n\t\t\tif ( data != null )\r\n\t\t\t{\r\n\t\t\t\tCredits = data.Credits;\r\n\t\t\t\tForceLevel = data.ForceLevel;\r\n\t\t\t\tMassLevel = data.MassLevel;\r\n\t\t\t\tEnergyLevel = data.EnergyLevel;\r\n\t\t\t\tPrestigeLevel = data.PrestigeLevel;\r\n\t\t\t\tPrestigeTokens = data.PrestigeTokens;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate class ProgressData\r\n\t{\r\n\t\tpublic int Credits { get; set; }\r\n\t\tpublic int ForceLevel { get; set; }\r\n\t\tpublic int MassLevel { get; set; }\r\n\t\tpublic int EnergyLevel { get; set; }\r\n\t\tpublic int PrestigeLevel { get; set; }\r\n\t\tpublic int PrestigeTokens { get; set; }\r\n\t}\r\n}"
},
{
"Ident": "cherkbyte.one_more_push",
"Path": "ModeController.cs",
"FileName": "ModeController.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 410999,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\npublic enum GameMode\r\n{\r\n SumoPush,\r\n NextbotRun\r\n}\r\n\r\npublic enum Language\r\n{\r\n English,\r\n Russian\r\n}\r\n\r\npublic sealed class ModeController : Component\r\n{\r\n public static ModeController Instance { get; private set; }\r\n\r\n [Property] public GameMode SelectedMode { get; set; } = GameMode.SumoPush;\r\n [Property] public Language CurrentLanguage { get; set; } = Language.Russian;\r\n [Property] public float MasterVolume { get; set; } = 1.0f;\r\n\r\n [Property] public SceneFile SumoScene { get; set; }\r\n [Property] public SceneFile NextbotScene { get; set; }\r\n\r\n protected override void OnAwake()\r\n {\r\n Instance = this;\r\n }\r\n\r\n public void SelectMode( GameMode mode )\r\n {\r\n SelectedMode = mode;\r\n\r\n if ( mode == GameMode.SumoPush )\r\n {\r\n if ( SumoScene != null )\r\n Game.ActiveScene.Load( SumoScene );\r\n else\r\n Game.ActiveScene.LoadFromFile( \"scenes/onemore.scene\" );\r\n }\r\n else if ( mode == GameMode.NextbotRun )\r\n {\r\n if ( NextbotScene != null )\r\n Game.ActiveScene.Load( NextbotScene );\r\n else\r\n Game.ActiveScene.LoadFromFile( \"scenes/onemores2.scene\" );\r\n }\r\n }\r\n\r\n public void ToggleLanguage()\r\n {\r\n CurrentLanguage = CurrentLanguage == Language.Russian ? Language.English : Language.Russian;\r\n }\r\n\r\n public string GetText( string key )\r\n {\r\n bool isRu = CurrentLanguage == Language.Russian;\r\n\r\n return key switch\r\n {\r\n \"game_title\" => \"ONE MORE PUSH\",\r\n \"title\" => isRu ? \"\u0412\u042b\u0411\u0415\u0420\u0418\u0422\u0415 \u0420\u0415\u0416\u0418\u041c\" : \"SELECT GAME MODE\",\r\n \"subtitle\" => isRu ? \"\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0435\u0436\u0438\u043c \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0439\u0442\u0435\u0441\u044c \u043d\u0430 \u0430\u0440\u0435\u043d\u0443\" : \"Choose your mode and jump into action\",\r\n \"sumo_title\" => \"SUMO PUSH\",\r\n \"sumo_tag\" => isRu ? \"\u041a\u041b\u0410\u0421\u0421\u0418\u041a\u0410\" : \"CLASSIC\",\r\n \"sumo_desc\" => isRu ? \"\u0422\u043e\u043b\u043a\u0430\u0439 \u0432\u0440\u0430\u0433\u043e\u0432 \u0438 \u0431\u043e\u0442\u043e\u0432 \u0441 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b, \u043f\u043e\u043a\u0430 \u043e\u043d\u0430 \u0441\u0443\u0436\u0430\u0435\u0442\u0441\u044f!\" : \"Push enemies and bots off the platform before it shrinks!\",\r\n \"nextbot_title\" => \"NEXTBOT RUN\",\r\n \"nextbot_tag\" => isRu ? \"\u0421\u041a\u041e\u0420\u041e\" : \"COMING SOON\",\r\n \"nextbot_desc\" => isRu ? \"\u0423\u0431\u0435\u0433\u0430\u0439 \u043e\u0442 \u043d\u0435\u043a\u0441\u0442\u0431\u043e\u0442\u043e\u0432, \u0441\u043f\u0430\u0441\u0430\u0439 \u043d\u043e\u043a\u0430\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u044e\u0437\u043d\u0438\u043a\u043e\u0432 \u0438 \u0432\u044b\u0436\u0438\u0432\u0430\u0439!\" : \"Run from nextbots, revive fallen teammates, and survive!\",\r\n \"lang_btn\" => isRu ? \"RU\" : \"EN\",\r\n \"settings_btn\" => isRu ? \"\u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418\" : \"SETTINGS\",\r\n \"exit_btn\" => isRu ? \"\u0412\u042b\u0425\u041e\u0414\" : \"EXIT\",\r\n \"settings_title\" => isRu ? \"\u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418\" : \"SETTINGS\",\r\n \"volume_label\" => isRu ? \"\u0413\u0440\u043e\u043c\u043a\u043e\u0441\u0442\u044c \u0437\u0432\u0443\u043a\u0430\" : \"Master Volume\",\r\n \"controls_title\" => isRu ? \"\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\" : \"Controls\",\r\n \"ctrl_move\" => isRu ? \"WASD \u2014 \u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435\" : \"WASD \u2014 Movement\",\r\n \"ctrl_action\" => isRu ? \"\u041b\u041a\u041c / E \u2014 \u0410\u0442\u0430\u043a\u0430 / \u0421\u043f\u0430\u0441\u0435\u043d\u0438\u0435\" : \"LMB / E \u2014 Push / Revive\",\r\n \"ctrl_space\" => isRu ? \"\u041f\u0440\u043e\u0431\u0435\u043b \u2014 \u041f\u0440\u044b\u0436\u043e\u043a\" : \"Space \u2014 Jump\",\r\n \"close_btn\" => isRu ? \"\u0417\u0410\u041a\u0420\u042b\u0422\u042c\" : \"CLOSE\",\r\n \"news_title\" => isRu ? \"\u041d\u041e\u0412\u041e\u0421\u0422\u0418 \u0418 \u041e\u0411\u041d\u041e\u0412\u041b\u0415\u041d\u0418\u042f\" : \"NEWS & UPDATES\",\r\n \"news_tag_new\" => isRu ? \"\u041f\u0410\u0422\u0427 1.2\" : \"PATCH 1.2\",\r\n \"news_item1_title\" => isRu ? \"\u0420\u0435\u0436\u0438\u043c Nextbot Run\" : \"Nextbot Run Mode\",\r\n \"news_item1_desc\" => isRu ? \"\u0420\u0435\u0436\u0438\u043c \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0438 \u0441\u043a\u043e\u0440\u043e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d!\" : \"Mode in development and coming soon!\",\r\n \"news_item2_title\" => isRu ? \"\u041e\u0431\u043d\u043e\u0432\u043b\u0451\u043d\u043d\u044b\u0439 Glass UI\" : \"New Glass UI\",\r\n \"news_item2_desc\" => isRu ? \"\u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0432\u0443\u043a\u0430, \u0432\u044b\u0431\u043e\u0440 \u044f\u0437\u044b\u043a\u0430 \u0438 \u044d\u043a\u0440\u0430\u043d \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.\" : \"Sleek glass UI design, audio settings, multi-language support & loading screen.\",\r\n \"news_item3_title\" => isRu ? \"\u0411\u0430\u043b\u0430\u043d\u0441 Sumo Push\" : \"Sumo Push Balance\",\r\n \"news_item3_desc\" => isRu ? \"\u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0430 \u0444\u0438\u0437\u0438\u043a\u0430 \u0442\u043e\u043b\u0447\u043a\u043e\u0432 \u0438 \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0430 \u0434\u0438\u043d\u0430\u043c\u0438\u043a\u0430 \u0441\u0443\u0436\u0435\u043d\u0438\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b!\" : \"Improved push physics and faster platform shrinking dynamics!\",\r\n \"loading_title\" => isRu ? \"\u0417\u0410\u0413\u0420\u0423\u0417\u041a\u0410 \u0410\u0420\u0415\u041d\u042b...\" : \"LOADING ARENA...\",\r\n \"loading_sub\" => isRu ? \"\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0438 \u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432...\" : \"Preparing geometry and spawning objects...\",\r\n \"tip_label\" => isRu ? \"\u0421\u041e\u0412\u0415\u0422:\" : \"TIP:\",\r\n \"tip_sumo\" => isRu ? \"\u0421\u043b\u0435\u0434\u0438\u0442\u0435 \u0437\u0430 \u043c\u0438\u0433\u0430\u044e\u0449\u0438\u043c\u0438 \u0431\u043b\u043e\u043a\u0430\u043c\u0438 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u2014 \u043e\u043d\u0438 \u0443\u043f\u0430\u0434\u0443\u0442 \u043f\u0435\u0440\u0432\u044b\u043c\u0438!\" : \"Watch out for flashing platform blocks \u2014 they fall first!\",\r\n \"tip_nextbot\" => isRu ? \"\u0414\u0435\u0440\u0436\u0430\u0442\u044c\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0441\u043e\u044e\u0437\u043d\u0438\u043a\u0430\u043c\u0438 \u043f\u0440\u043e\u0449\u0435: \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043f\u043e\u0434\u043d\u044f\u0442\u044c \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430!\" : \"Stick together with teammates: reviving is much faster when close!\",\r\n _ => key\r\n };\r\n }\r\n}"
}
]
}