GameMode/BuildingManager.cs

A game component that manages player-built objects (turrets and teleporters). It exposes server RPCs to request or destroy specific building prefabs, spawns clones on the host, tracks them per player in a networked dictionary, and returns weapons when turrets are removed.

Networking
using Sandbox;
using Sandbox.Diagnostics;

namespace KOTH;

public sealed class BuildingManager : Component
{
	[Property] public GameObject TurretPrefab { get; private set; }
	[Property] public GameObject EnterTeleporterPrefab { get; private set; }
	[Property] public GameObject ExitTeleporterPrefab { get; private set; }

	[Sync(SyncFlags.FromHost)] public NetDictionary<PlayerState, List<GameObject>> PlayerBuildings { get; private set; } = new();

	[Rpc.Host]
	public void ServerRequestTurret(PlayerState RequestingState)
	{
		if (RequestingState == null || RequestingState.PlayerPawn == null)
		{
			return;
		}

		Build_ServerOnly(TurretPrefab, RequestingState);
	}

	[Rpc.Host]
	public void ServerDestroyTurret(PlayerState RequestingState)
	{
		if (RequestingState == null)
		{
			return;
		}

		foreach (var Building in PlayerBuildings.GetOrCreate(RequestingState))
		{
			if (Building.GetComponent<TurretComponent>() != null)
			{
				Destroy_ServerOnly(Building, RequestingState);
				return;
			}
		}
	}

	[Rpc.Host]
	public void ServerRequestEnterTeleporter(PlayerState RequestingState)
	{
		if (RequestingState == null || RequestingState.PlayerPawn == null)
		{
			return;
		}

		Build_ServerOnly(EnterTeleporterPrefab, RequestingState);
	}

	[Rpc.Host]
	public void ServerDestroyEnterTeleporter(PlayerState RequestingState)
	{
		if (RequestingState == null)
		{
			return;
		}

		foreach (var Building in PlayerBuildings.GetOrCreate(RequestingState))
		{
			if (Building.GetComponent<TeleporterEntrenceComponent>() != null)
			{
				Destroy_ServerOnly(Building, RequestingState);
				return;
			}
		}
	}

	[Rpc.Host]
	public void ServerRequestExitTeleporter(PlayerState RequestingState)
	{
		if (RequestingState == null || RequestingState.PlayerPawn == null)
		{
			return;
		}

		Build_ServerOnly(ExitTeleporterPrefab, RequestingState);
	}

	[Rpc.Host]
	public void ServerDestroyExitTeleporter(PlayerState RequestingState)
	{
		if (RequestingState == null)
		{
			return;
		}

		foreach (var Building in PlayerBuildings.GetOrCreate(RequestingState))
		{
			if (Building.GetComponent<TeleporterExitComponent>() != null)
			{
				Destroy_ServerOnly(Building, RequestingState);
				return;
			}
		}
	}

	private void Build_ServerOnly(GameObject BuildingPrefab, PlayerState RequestingState)
	{
		Assert.True(Networking.IsHost);

		if (RequestingState == null || RequestingState.PlayerPawn == null)
		{
			return;
		}

		var RequestingPawn = RequestingState.PlayerPawn;

		Transform SpawnTransform = new()
		{
			Position = RequestingPawn.CenterPosition + (RequestingPawn.AimRay.Forward * 128),
			Rotation = RequestingPawn.Boom.WorldRotation
		};

		var BuildingClone = BuildingPrefab.Clone(SpawnTransform);
		var BuildingCloneComponent = BuildingClone.Components.Get<BuildingComponent>();
		BuildingCloneComponent.OwnerState = RequestingState;

		// if it's a turret we want to yoink the gun & setup some stats
		if (BuildingCloneComponent is TurretComponent TurretComponent)
		{
			if (!RequestingPawn.Inventory.CurrentWeaponGameObject.IsValid())
			{
				Log.Error("failed to spawn turret bc of some bullshit");
				BuildingClone.Destroy();
				return;
			}

			TurretComponent.SetFromWeaponGameObject(RequestingPawn.Inventory.CurrentWeaponGameObject);
			RequestingPawn.Inventory.RemoveWeapon(RequestingPawn.CurrentEquipment);
		}

		Assert.True(BuildingClone.NetworkSpawn());

		var PlayerBuildingsObjects = PlayerBuildings.GetOrCreate(RequestingState);
		PlayerBuildingsObjects.Add(BuildingClone);
	}

	private void Destroy_ServerOnly(GameObject BuildingToDestroy, PlayerState RequestingState)
	{
		Assert.True(Networking.IsHost);

		if (RequestingState == null)
		{
			return;
		}

		var PlayerBuildingsObjects = PlayerBuildings.GetOrCreate(RequestingState);
		PlayerBuildingsObjects.Remove(BuildingToDestroy);

		// if it's a turret give back to the world, if it's real
		if (BuildingToDestroy.GetComponent<TurretComponent>() is TurretComponent TurretComponent && RequestingState.PlayerPawn != null)
		{
			if (TurretComponent.EquippedSlot == EEquipmentSlot.Primary)
			{
				RequestingState.PlayerPawn.Inventory.Give(RequestingState.PlayerPawn.PlayerPawnDefinition.CharacterDefinition.PrimaryWeapon, false);
			}
			else if (TurretComponent.EquippedSlot == EEquipmentSlot.Secondary)
			{
				RequestingState.PlayerPawn.Inventory.Give(RequestingState.PlayerPawn.PlayerPawnDefinition.CharacterDefinition.SecondaryWeapon, false);
			}
		}

		BuildingToDestroy.Destroy();
	}
}