DemoPlayer.cs
using Sandbox.Citizen;
using Sandbox.Events;
using Sandbox.Movement;
using Sandbox.Sboku;
using Sandbox.Sboku.Arena;
using Sandbox.Sboku.UI;
using SWB.Base;
using SWB.Demo.Clothing;
using SWB.HUD;
using SWB.Player;
using SWB.Shared;
using SWB.Visuals;
using SWB.Visuals.Profiles;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace SWB.Demo;
[Group("SWB")]
[Title("Demo Player")]
public partial class DemoPlayer : PlayerBase,
IGameEventHandler<RoundManager.OpenUpgradeScreen>,
IGameEventHandler<RoundManager.Victory>
{
private bool IsPaused;
private PauseMenu PauseMenu;
public enum MovementMode
{
Grid,
Free
}
private bool IsStartMenuOpen;
public MovementMode CurrentMovementMode { get; private set; } = MovementMode.Free;
[Property]
public GameObject ArenaUI { get; set; }
/// <summary>
/// Grid-pelimuodissa ei käytetä upgradeja
/// </summary>
public bool UsesUpgrades => false;
private GridMap GridMap
{
get
{
return Scene.GetComponentInChildren<GridSpawner>()?.Map;
}
}
protected override void OnAwake()
{
base.OnAwake();
}
// -------------------------------------------------
// START / UI
// -------------------------------------------------
protected override void OnStart()
{
base.OnStart();
if (ArenaUI.IsValid())
ArenaUI.Enabled = false;
if (!IsValid)
return;
var grid = GameObject.Components.GetOrCreate<GridMovement>();
var free = Components.Get<CameraMovement>();
ApplyMovementMode();
if (ArenaUI.IsValid())
{
ArenaUI.Enabled = true;
PauseMenu = ArenaUI.Components.GetOrCreate<PauseMenu>();
}
// 👇 post process kameraan
if (Camera != null)
{
Camera.GameObject.Components.GetOrCreate<DarkProfile>();
}
}
void DebugNavMeshBounds()
{
var nav = Scene.NavMesh;
if (nav == null) return;
Vector3 min = new(float.MaxValue);
Vector3 max = new(float.MinValue);
for (int x = -10000; x <= 10000; x += 200)
for (int y = -10000; y <= 10000; y += 200)
{
var p = new Vector3(x, y, 1000);
var hit = nav.GetClosestPoint(p, 2000f);
if (hit.HasValue)
{
min = Vector3.Min(min, hit.Value);
max = Vector3.Max(max, hit.Value);
}
}
DebugOverlay.Box((min + max) * 0.5f, max - min, Color.Green, 30f);
Log.Info($"NavMesh bounds: {min} -> {max}");
}
protected override void OnUpdate()
{
base.OnUpdate();
if (IsRagdolled)
return;
if (IsProxy || !IsValid)
return;
if (Input.Pressed("toggleconsole"))
{
// älä tee mitään
}
// ESC → pause
if (Input.Pressed("pause_menu"))
{
TogglePause();
return;
}
if (IsPaused)
return;
// 🔒 Lukitse body pystysuoraan (FPS)
var yaw = Rotation.FromYaw(Camera.Transform.Rotation.Yaw());
Transform.Rotation = yaw;
if (Input.Pressed("toggle_movement")) // X
{
ToggleMovementMode();
}
}
private void TogglePause()
{
if (IsPaused)
ResumeGame();
else
PauseGame();
}
private void PauseGame()
{
IsPaused = true;
Mouse.Visibility = MouseVisibility.Visible;
PauseMenu?.Show();
if (Camera != null) Camera.Enabled = false;
if (ViewModelCamera != null) ViewModelCamera.Enabled = false;
var grid = Components.Get<GridMovement>();
if (grid != null)
grid.IgnoreInput = true;
var cam = Components.Get<CameraMovement>();
if (cam != null)
cam.Enabled = false;
var vmh = Components.Get<ViewModelHandler>();
if (vmh != null)
vmh.Enabled = false;
}
public void ResumeGame()
{
IsPaused = false;
Mouse.Visibility = MouseVisibility.Hidden;
PauseMenu?.Hide();
if (Camera != null) Camera.Enabled = true;
if (IsProxy && ViewModelCamera != null)
{
ViewModelCamera.Enabled = true;
}
ApplyMovementMode();
var grid = Components.Get<GridMovement>();
if (grid != null)
grid.IgnoreInput = false;
if (IsProxy)
{
var vmh = Components.Get<ViewModelHandler>();
if (vmh != null)
{
vmh.Enabled = true;
vmh.Reset(); // ⬅️ tärkeä
}
}
}
private void ToggleMovementMode()
{
var grid = Components.Get<GridMovement>();
var free = Components.Get<CameraMovement>();
if (grid == null)
return;
if (CurrentMovementMode == MovementMode.Grid)
{
// GRID → FREE
CurrentMovementMode = MovementMode.Free;
grid.EnterFreeMode();
if (free != null)
free.Enabled = true;
}
else
{
// FREE → GRID
CurrentMovementMode = MovementMode.Grid;
if (free != null)
free.Enabled = false;
grid.EnterGridMode(); // 🔑 TÄMÄ KORJAA ONGELMAN
}
}
// -------------------------------------------------
// RESPAWN
// -------------------------------------------------
public override void Respawn()
{
IsDead = false;
Health = MaxHealth;
var spawn = GetSpawnLocation();
WorldPosition = spawn.Position;
EyeAngles = spawn.Rotation.Angles();
Network.ClearInterpolation();
// 🔑 sama objekti herää henkiin
BodyRenderer.Enabled = true;
AnimationHelper.Enabled = true;
Inventory.Clear();
GiveWeapon("swb_veresk", true);
_ = RespawnAfterGridReady();
}
private async Task RespawnAfterGridReady()
{
// 🔑 VAIN SERVER ODOTTAA GRIDMAPPIA
if (!IsProxy)
{
for (int i = 0; i < 10; i++)
{
if (GridMap != null)
break;
await Task.Delay(1);
}
if (GridMap == null)
{
Log.Error("[DemoPlayer] GridMap never became available");
return;
}
WorldPosition = GetRandomPlayerSpawn();
var grid = Components.Get<GridMovement>();
grid?.SnapToGrid();
}
// 🎮 CLIENT-ONLY ASIAT
Mouse.Visibility = MouseVisibility.Hidden;
// SERVER
if (!IsProxy && Inventory.Items.Count == 0)
{
GiveWeapon("swb_veresk", true); // setActive = true
}
}
public override async void RespawnWithDelay(float delay)
{
await GameTask.DelaySeconds(delay);
// ❌ EI GameOveria
// ✅ vain tämä pelaaja respawnaa
Respawn();
// 🔑 Palauta input clientillä seuraavassa framessa
await GameTask.Delay(1);
RestoreClientInput();
}
private void RestoreClientInput()
{
if (IsProxy) // client
{
Mouse.Visibility = MouseVisibility.Hidden;
BodyRenderer.RenderType = ModelRenderer.ShadowRenderType.ShadowsOnly;
var movement = Components.Get<CameraMovement>();
if (movement != null)
movement.Enabled = true;
if (Camera != null)
Camera.Enabled = true;
if (ViewModelCamera != null)
ViewModelCamera.Enabled = true;
}
}
// -------------------------------------------------
// GAME EVENTS
// -------------------------------------------------
/// <summary>
/// Upgrade-event on pakko implementoida,
/// mutta grid-modessa se IGNOROIDAAN täysin.
/// </summary>
public void OnGameEvent(RoundManager.OpenUpgradeScreen eventArgs)
{
// Intentionally empty
// Älä dispatchaa UpgradeScreenClosed
// Älä avaa UI:ta
}
public void OnGameEvent(RoundManager.Victory eventArgs)
{
if (!IsValid || IsProxy)
return;
//CreateVictoryScreen();
}
// -------------------------------------------------
// END SCREENS
// -------------------------------------------------
public void CreateFailScreen()
{
if (ArenaUI.IsValid())
ArenaUI.AddComponent<EndMenu>();
}
public void CreateVictoryScreen()
{
if (!ArenaUI.IsValid())
return;
var menu = ArenaUI.AddComponent<EndMenu>();
menu.WinVariant = true;
}
// -------------------------------------------------
// CLEANUP
// -------------------------------------------------
protected override void OnDestroy()
{
base.OnDestroy();
}
public void GiveWeapon(string className, bool setActive = true)
{
var weapon = WeaponRegistry.Instance.Get(className);
if (weapon == null)
{
Log.Error($"[DemoPlayer] Weapon '{className}' not found!");
return;
}
// Lisää ase inventoryyn
var weaponGO = Inventory.AddClone(weapon.GameObject, false);
if (weaponGO == null)
return;
// 🔫 AKTIVOI ASE KÄTEEN
if (setActive)
{
Inventory.SetActive(weaponGO);
}
SetAmmo(weapon.Primary.AmmoType, 360);
Log.Info($"Inventory items: {Inventory.Items.Count}");
Log.Info($"Active item: {Inventory.Active?.Name}");
}
private Vector3 GetRandomPlayerSpawn()
{
var prisonSpawns = Scene.GetAllComponents<PrisonSpawnMarker>()
.Where(m => m.IsValid && m.PreferForPlayers)
.Select(m => m.GameObject.WorldPosition + Vector3.Up * 64f)
.ToList();
if (prisonSpawns.Count > 0)
{
// Etsi toinen elossa oleva pelaaja (jos on)
var otherPlayer = Scene.GetAllComponents<DemoPlayer>()
.FirstOrDefault(p => p != this && p.IsValid);
// Jos ei ole toista pelaajaa → satunnainen spawn
if (otherPlayer == null)
{
return prisonSpawns[Game.Random.Int(0, prisonSpawns.Count - 1)];
}
// Valitse kauimmaisin spawn suhteessa toiseen pelaajaan
Vector3 bestPos = WorldPosition;
float bestDistSqr = -1f;
foreach (var worldPos in prisonSpawns)
{
float distSqr = worldPos.DistanceSquared(otherPlayer.Camera.WorldPosition);
if (distSqr > bestDistSqr)
{
bestDistSqr = distSqr;
bestPos = worldPos;
}
}
return bestPos;
}
var map = GridMap;
if (map == null)
{
Log.Warning("[DemoPlayer] No prison spawn markers or GridMap found, using current position");
return WorldPosition;
}
var spawnCells = map.GetAllSpawnPoints('@')
.Where(c => map.IsWalkable(c.x, c.y))
.ToList();
if (spawnCells.Count == 0)
{
Log.Warning("[DemoPlayer] No '@' spawn points found");
return WorldPosition;
}
var fallbackPlayer = Scene.GetAllComponents<DemoPlayer>()
.FirstOrDefault(p => p != this && p.IsValid);
if (fallbackPlayer == null)
{
var cell = spawnCells[Game.Random.Int(0, spawnCells.Count - 1)];
return map.GridToWorld(cell.x, cell.y, 64f);
}
Vector3 fallbackBest = WorldPosition;
float fallbackBestDistSqr = -1f;
foreach (var cell in spawnCells)
{
var worldPos = map.GridToWorld(cell.x, cell.y, 64f);
float distSqr = worldPos.DistanceSquared(fallbackPlayer.Camera.WorldPosition);
if (distSqr > fallbackBestDistSqr)
{
fallbackBestDistSqr = distSqr;
fallbackBest = worldPos;
}
}
return fallbackBest;
}
[Rpc.Host]
public void ApplyEnvironmentalDamage(int damage)
{
if (IsProxy)
return;
Health -= damage;
if (Health <= 0)
{
Health = 0;
OnKilledByEnvironment();
}
}
private void OnKilledByEnvironment()
{
// vain tämä pelaaja kuolee
Respawn();
}
private void ApplyMovementMode()
{
var grid = Components.Get<GridMovement>();
var free = Components.Get<CameraMovement>();
if (grid == null)
return;
if (CurrentMovementMode == MovementMode.Free)
{
// 🔑 FREE MODE
grid.Enabled = false; // ⬅️ TÄMÄ PUUTTUI
grid.IgnoreInput = false;
if (free != null)
free.Enabled = true;
grid.EnterFreeMode();
}
else
{
// 🔑 GRID MODE
if (free != null)
free.Enabled = false;
grid.Enabled = true; // ⬅️ JA TÄMÄ
grid.IgnoreInput = false;
grid.EnterGridMode();
}
}
public async Task DestroyAllUIAsync()
{
// odota että kaikki tämän framen eventit on ajettu
await GameTask.Delay(1);
if (!ArenaUI.IsValid())
return;
foreach (var panel in ArenaUI.Components.GetAll<ScreenPanel>())
{
panel.GameObject.Destroy();
}
ArenaUI.Enabled = false;
IsStartMenuOpen = false;
}
[Rpc.Broadcast]
public override void OnDeath(Shared.DamageInfo info)
{
if (IsDead) return;
IsDead = true;
Health = 0;
SpawnDeathModel(info.Force, info.Origin);
// 🔑 pelaaja piiloon, EI POIS
BodyRenderer.Enabled = false;
AnimationHelper.Enabled = false;
CharacterController.Velocity = 0;
// 🔑 botit näkevät yhä targetin
RespawnWithDelay(2f);
}
private void SpawnDeathModel(Vector3 force, Vector3 origin)
{
var death = Scene.CreateObject();
death.Name = "PlayerDeath";
death.WorldPosition = Body.WorldPosition;
death.WorldRotation = Body.WorldRotation;
// renderer
var smr = death.Components.Create<SkinnedModelRenderer>();
smr.Model = BodyRenderer.Model;
// 🧥 vaatteet
ClothingBuilder.Build(
smr,
clothingJSON, // 🔑 käytä pelaajan omia vaatteita
null,
null,
null,
null,
null,
null
);
// physics
var phys = death.Components.Create<ModelPhysics>();
phys.Renderer = smr;
phys.Enabled = true;
if (phys.PhysicsGroup != null)
{
foreach (var body in phys.PhysicsGroup.Bodies)
body.ApplyImpulseAt(origin, force);
}
// estä animaatiot
foreach (var anim in death.Components.GetAll<CitizenAnimationHelper>())
anim.Enabled = false;
}
}