Sboku/BotAdapter.cs
using Sandbox;
using Sandbox.Citizen;
using Sandbox.Events;
using Sandbox.Sboku.Arena;
using Sandbox.Sboku.Shared;
using Sandbox.Shared;
using SWB.Base;
using SWB.Demo;
using SWB.Demo.Clothing;
using SWB.Player;
using SWB.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sandbox.Sboku;
[Title("Bot Adapter")]
[Group("Sboku Arena")]
public class BotAdapter : SbokuBase,
IPlayerBase,
IGameEventHandler<Weapon.NoAmmoLeftEvent>,
IGameEventHandler<Weapon.ReloadFinished>
{
[Sync]
public int BotIndex { get; set; }
// ==============================
// INVENTORY / WEAPON
// ==============================
public override ISbokuWeapon Weapon => sbokuWeapon;
private ISbokuWeapon sbokuWeapon;
public IInventory Inventory { get; set; }
public void GiveWeapon(string className)
{
var weapon = WeaponRegistry.Instance.Get(className);
if (weapon == null)
{
Log.Error($"[BotAdapter] Weapon '{className}' not found");
return;
}
Inventory.AddClone(weapon.GameObject, true);
SetAmmo(weapon.Primary.AmmoType, 999);
sbokuWeapon = new WeaponAdapter(weapon);
}
// ==============================
// LIFECYCLE
// ==============================
protected override void OnStart()
{
Log.Info($"[BOT {BotIndex}] OnStart | IsProxy={IsProxy} | Scene={Scene.Name}");
if (IsProxy)
return;
Health = MaxHealth;
Inventory = Components.Create<Inventory>();
Camera = AddComponent<CameraComponent>(false);
ViewModelCamera = AddComponent<CameraComponent>(false);
var weapon = WeaponRegistry.Instance.Weapons.Values
.ElementAt(Game.Random.Next(WeaponRegistry.Instance.Weapons.Count));
GiveWeapon(weapon.ClassName);
// ==============================
// 🎨 VAATTEET (VISUAALIT)
// ==============================
var smr = GetComponentInChildren<SkinnedModelRenderer>();
if (smr != null)
{
BodyRenderer = ClothingBuilder.Build(
smr,
"", // head
"hat/Racer_Helmet/racer_helmet",
"shirt/Jumpsuit/prison_jumpsuit",
null,
"",
"",
null
);
}
CreateHitCollider(); // 🔥 LISÄÄ TÄMÄ
}
private void CreateHitCollider()
{
var hit = Scene.CreateObject();
hit.Name = "HitCollider";
hit.Parent = GameObject;
// Suunnilleen rinnan kohdalle
hit.LocalPosition = new Vector3(0, 0, 36f);
hit.LocalRotation = Rotation.Identity;
var sphere = hit.Components.Create<SphereCollider>();
sphere.Radius = 24f;
sphere.IsTrigger = false;
// 🔑 TÄRKEIN RIVI
hit.Tags.Add(TagsHelper.Player);
}
protected override void OnUpdate()
{
if (!IsAlive)
{
Enabled = false;
return;
}
if (Head != null)
{
Head.WorldRotation = EyeAngles.ToRotation();
}
base.OnUpdate();
}
// ==============================
// INPUT → SWB
// ==============================
public bool IsAttackDown(string type)
=> IsAlive && IsShooting;
public bool IsAttackPressed(string type)
=> IsAlive && IsShooting;
public bool IsReloadDown()
=> IsReloading;
public void ShakeScreen(ScreenShake shake) { }
// ==============================
// WEAPON EVENTS
// ==============================
public void OnGameEvent(Weapon.NoAmmoLeftEvent e)
=> Reload();
public void OnGameEvent(Weapon.ReloadFinished e)
=> OnReloadFinish();
// ==============================
// IPlayerBase – PROPERTIES
// ==============================
public bool IsFirstPerson => false;
public bool IsBot => true;
public bool IsAlive => Health > 0;
public CameraComponent Camera { get; set; }
public CameraComponent ViewModelCamera { get; set; }
public float InputSensitivity { get; set; }
public Angles EyeAnglesOffset { get; set; }
[Sync] public bool IsCrouching { get; set; }
[Sync] public bool IsRunning { get; set; }
[Sync] public int Health { get; set; }
[Sync] public int Kills { get; set; }
[Sync] public int Deaths { get; set; }
Vector3 IPlayerBase.Velocity => Character?.Velocity ?? Vector3.Zero;
bool IPlayerBase.IsOnGround => Character?.IsOnGround ?? true;
Guid IPlayerBase.Id => GameObject.Id;
// ==============================
// DAMAGE
// ==============================
public void TakeDamage(SWB.Shared.DamageInfo info)
{
if (IsProxy || !IsAlive)
return;
Health -= (int)info.Damage;
if (Health <= 0)
{
Die(info);
}
}
private void Die(SWB.Shared.DamageInfo info)
{
Health = 0;
Deaths++;
ResetState();
// 🔑 KUTSU BROADCASTIA
OnDeathBroadcast(
info.Force,
info.Origin
);
}
[Rpc.Broadcast]
private void OnDeathBroadcast(Vector3 force, Vector3 origin)
{
SpawnRagdoll(force, origin);
// 🔑 POISTA ELÄVÄ BOTTI KAIKILTA
GameObject.Destroy();
}
private void SpawnRagdoll(Vector3 force, Vector3 origin)
{
Log.Info("SPawndagdoll");
var ragdoll = Scene.CreateObject();
ragdoll.Name = "BotRagdoll";
ragdoll.WorldPosition = GameObject.WorldPosition;
ragdoll.WorldRotation = GameObject.WorldRotation;
// =========================
// RENDERER
// =========================
var smr = ragdoll.Components.Create<SkinnedModelRenderer>();
smr.Model = BodyRenderer.Model;
// 🔑 VAATTEET UUDELLE MODELILLE
BodyRenderer = ClothingBuilder.Build(
smr,
"", // head
"hat/Racer_Helmet/racer_helmet",
"shirt/Jumpsuit/prison_jumpsuit",
null,
"",
"",
null
);
// =========================
// PHYSICS
// =========================
var phys = ragdoll.Components.Create<ModelPhysics>();
phys.Renderer = smr;
phys.Enabled = true;
if (phys.PhysicsGroup != null)
{
foreach (var body in phys.PhysicsGroup.Bodies)
body.ApplyImpulseAt(origin, force);
}
// =========================
// VARMISTA EI ANIMAATIOTA
// =========================
foreach (var anim in ragdoll.Components.GetAll<CitizenAnimationHelper>())
anim.Enabled = false;
}
// ==============================
// CHARACTER / VIEW
// ==============================
[Property] public GameObject Head { get; set; }
[Property] public GameObject Body { get; set; }
[Property] public SkinnedModelRenderer BodyRenderer { get; set; }
[Property] public CitizenAnimationHelper AnimationHelper { get; set; }
[Property] public ModelPhysics RagdollPhysics { get; set; }
[Property] public int MaxHealth { get; set; } = 100;
[Sync] public Vector3 EyeOffset { get; set; } = Vector3.Zero;
[Sync]
public override Angles EyeAngles
{
get => eyeAngles;
set => eyeAngles = value;
}
private Angles eyeAngles;
public override Vector3 EyePos => Head.WorldPosition + EyeOffset;
// ==============================
// ANIMATION
// ==============================
protected override void UpdateAnimations(Vector3 wishVelocity, Rotation rotation)
{
if (AnimationHelper == null)
return;
AnimationHelper.WithWishVelocity(wishVelocity);
AnimationHelper.WithVelocity(Character.Velocity);
AnimationHelper.AimAngle = rotation;
AnimationHelper.IsGrounded = Character.IsOnGround;
AnimationHelper.WithLook(rotation.Forward);
AnimationHelper.MoveStyle = CitizenAnimationHelper.MoveStyles.Run;
}
// ==============================
// AMMO
// ==============================
[Sync] public NetDictionary<string, int> Ammo { get; set; } = new();
public int AmmoCount(string type)
=> Ammo.TryGetValue(type, out var a) ? a : 0;
public void SetAmmo(string type, int amount)
=> Ammo[type] = amount;
public int TakeAmmo(string type, int amount)
{
var available = AmmoCount(type);
amount = Math.Min(amount, available);
SetAmmo(type, available - amount);
return amount;
}
protected override List<ISbokuCondition> GetConditions()
{
return new()
{
new FindNearestAlivePlayerCondition( this ),
// muut ehdot
};
}
}