Grid/Base/PrisonConcreteBlockSpawner.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Sandbox.Sboku;
public sealed class PrisonConcreteBlockSpawner : Component
{
[Property] public int MaxBlocks { get; set; } = 32;
[Property] public int MaxSpawnMarkers { get; set; } = 8;
[Property] public float BlockScaleMultiplier { get; set; } = 1f;
[Property] public int WallHealth { get; set; } = 62;
[Property] public float WallZOffset { get; set; } = -256f;
[Property] public float GroundProbeHeight { get; set; } = 900f;
[Property] public float GroundProbeDepth { get; set; } = 2000f;
[Property] public float GroundPadding { get; set; } = 0f;
private bool spawned;
protected override void OnStart()
{
if (spawned || !Networking.IsHost)
return;
SpawnBlocks();
SpawnMarkers();
spawned = true;
}
private void SpawnBlocks()
{
var model = Model.Load("models/dev/box.vmdl");
var material = Material.Load("assets/materials/concrete_layers/concrete_layers.vmat");
var blockPositions = BuildBlockPositions();
int count = 0;
foreach (var localPos in blockPositions)
{
if (count >= MaxBlocks)
break;
SpawnBlock(model, material, localPos);
count++;
}
}
private IEnumerable<Vector3> BuildBlockPositions()
{
const int columns = 8;
const int rows = 4;
const float xStart = -448f;
const float yStart = -224f;
const float xStep = 128f;
const float yStep = 128f;
const float jitter = 36f;
var positions = new List<Vector3>(columns * rows);
var rng = new Random();
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < columns; col++)
{
var x = xStart + col * xStep + Game.Random.Float(-jitter, jitter);
var y = yStart + row * yStep + Game.Random.Float(-jitter, jitter);
positions.Add(new Vector3(x, y, 0f));
}
}
return positions.OrderBy(_ => rng.Next()).ToArray();
}
private void SpawnMarkers()
{
var markerPositions = new[]
{
new Vector3(-256, -256, 0),
new Vector3(0, -256, 0),
new Vector3(256, -256, 0),
new Vector3(-256, 0, 0),
new Vector3(256, 0, 0),
new Vector3(-256, 256, 0),
new Vector3(0, 256, 0),
new Vector3(256, 256, 0),
};
int count = 0;
foreach (var localPos in markerPositions)
{
if (count >= MaxSpawnMarkers)
break;
var go = new GameObject($"PrisonSpawn_{count}");
go.SetParent(GameObject, false);
go.Transform.Position = GameObject.Transform.Position + localPos;
go.Transform.Rotation = Rotation.Identity;
go.Components.Create<PrisonSpawnMarker>();
go.NetworkSpawn();
count++;
}
}
private void SpawnBlock(Model model, Material material, Vector3 localPosition)
{
if (model == null)
return;
var go = new GameObject("ConcreteBlock");
go.SetParent(GameObject, false);
var renderer = go.Components.Create<ModelRenderer>();
renderer.Model = model;
renderer.MaterialOverride = material;
int wallColumns = Game.Random.Int(1, 3);
var gridSpawner = Scene.GetComponentInChildren<GridSpawner>();
float gridSize = gridSpawner?.Map?.GridSize ?? 128f;
float scaleXY = (gridSize * BlockScaleMultiplier) / MathF.Max(model.Bounds.Size.x, model.Bounds.Size.y);
float scaleZ = (gridSize * wallColumns) / model.Bounds.Size.z;
go.Transform.Scale = new Vector3(scaleXY, scaleXY, scaleZ);
var pos = GameObject.Transform.Position + localPosition;
var traceStart = pos + Vector3.Up * GroundProbeHeight;
var traceEnd = pos + Vector3.Down * GroundProbeDepth;
var tr = Scene.Trace.Ray(traceStart, traceEnd)
.WithoutTags("solid", "player", "trigger", "viewmodel")
.Run();
if (tr.Hit)
pos = tr.HitPosition;
float worldHeight = model.Bounds.Size.z * scaleZ;
pos.z = pos.z + worldHeight * 0.5f + GroundPadding + WallZOffset;
go.Transform.Position = pos;
go.Transform.Rotation = Rotation.FromYaw(Game.Random.Float(0f, 360f));
var collider = go.Components.Create<BoxCollider>();
collider.Static = true;
var wall = go.Components.Create<WallColumnDamage>();
wall.DebrisPool = gridSpawner?.DebrisPool;
wall.DustPrefab = gridSpawner?.DustPrefab;
wall.WallMaterial = material;
wall.MaxHealth = WallHealth;
wall.GridSize = gridSize;
wall.ColumnHeight = wallColumns;
wall.IsIndestructible = false;
go.Tags.Add("solid");
go.NetworkSpawn();
}
}