Grid/Base/WallSpawner.cs
using Sandbox;
using Sandbox.Sboku;
using Sandbox.Sboku.Arena;
using System;
using System.Collections.Generic;
using System.Numerics;

/// <summary>
/// WallSpawner – vastaa seinien ja ovien spawnaamisesta maailmaan.
    /// Sisältää talo-id:n rakentamisen (flood fill), seinäpylväiden spawnauksen,
/// ovien spawnauksen sekä ulkorajan seinien spawnauksen.
/// </summary>
internal class WallSpawner
{
    private readonly GridSpawner owner;

    private GridMap Map => owner.Map;
    private Model BoxModel => owner.BoxModel;
    private Material WallMaterialOuter => owner.WallMaterialOuter;
    private Material WallMaterialHouse => owner.WallMaterialHouse;
    private Dictionary<int, Material> HouseWallMaterials => owner.HouseWallMaterials;
    private int[,] HouseIds => owner.HouseIds;
    private DebrisVisualPool DebrisPool => owner.DebrisPool;
    private GameObject DustPrefab => owner.DustPrefab;
    private int WallHeight => owner.WallHeight;
    private float FloorZ => owner.FloorZ;
    private const float BorderUVFactor = 2.5f;

    public WallSpawner(GridSpawner owner)
    {
        this.owner = owner;
    }

    /// <summary>
    /// Spawnaa seinäpylvään tai oven annettuun grid-koordinaattiin.
    /// </summary>
    public void SpawnWallsAndDoors(int x, int y)
    {
        if (!Networking.IsHost)
            return;

        if (Map.IsIndestructible(x, y))
            return;


        int columnHeight = Game.Random.Int(1, WallHeight);

        var go = new GameObject($"Wall_{x}_{y}");

        var dmg = go.Components.Create<WallColumnDamage>();

        dmg.DebrisPool = DebrisPool;
        dmg.DustPrefab = DustPrefab;

        int houseId = HouseIds[x, y];
        if (houseId != 0 && HouseWallMaterials.TryGetValue(houseId, out var mat))
            dmg.WallMaterial = mat;
        else
            dmg.WallMaterial = WallMaterialHouse; // fallback
        dmg.MaxHealth = 1000;

        
        dmg.ColumnHeight = columnHeight;
        dmg.GridSize = Map.GridSize;
        dmg.IsIndestructible = false;

        var renderer = go.Components.Create<ModelRenderer>();
        renderer.Model = BoxModel;

        
        dmg.WallMaterial.Set("g_vTexCoordScale", new Vector2(1f, MathF.Max(columnHeight * 1.5f, 2.0f)));
        renderer.MaterialOverride = dmg.WallMaterial;

        // 🔑 skaala
        float scaleXY = Map.GridSize / BoxModel.Bounds.Size.x;
        float scaleZ = (Map.GridSize * columnHeight) / BoxModel.Bounds.Size.z;
        go.Transform.Scale = new Vector3(scaleXY, scaleXY, scaleZ);

        // 🔑 GRID → WORLD (Z = 0!)
        Vector3 pos = Map.GridToWorld(x, y, 0);

        // 🔑 pylvään world-korkeus
        float worldHeight = BoxModel.Bounds.Size.z * scaleZ;

        // 🔑 ALAPINTA LATTIAAN
        pos.z = FloorZ + worldHeight * 0.5f;

        go.Transform.Position = pos;

        var collider = go.Components.Create<BoxCollider>();
        collider.Static = true;

        /*var physics = go.Components.Create<ModelPhysics>();
        physics.Enabled = true;
        physics.MotionEnabled = false;*/

        go.Tags.Add("solid");

        if (Map.IsDoor(x, y))
        {
            var door = go.Components.Create<DoorComponent>();
            door.GridX = x;
            door.GridY = y;
            door.Activate();
        }

    }

    /// <summary>
    /// Spawnaa ulkorajan seinät (pohjoinen, etelä, itä, länsi).
    /// </summary>
    public void SpawnBorderWalls()
    {
        if (!Networking.IsHost)
            return;

        const int height = 2;

        float wallWorldHeight = Map.GridSize * height;
        float scaleZ = wallWorldHeight / BoxModel.Bounds.Size.z;

        float totalWidth = Map.Width * Map.GridSize;
        float totalHeight = Map.Height * Map.GridSize;

        float scaleX_Long = totalWidth / BoxModel.Bounds.Size.x;
        float scaleY_Long = totalHeight / BoxModel.Bounds.Size.y;

        float scaleX_Thin = Map.GridSize / BoxModel.Bounds.Size.x;
        float scaleY_Thin = Map.GridSize / BoxModel.Bounds.Size.y;

        // SOUTH
        {
            var center = Map.GridToWorld((int)(Map.Width / 2f - 0.5f), 0, 0);

            SpawnBorder(
                "Border_South",
                center,
                new Vector3(scaleX_Long, scaleY_Thin, scaleZ)
            );
        }

        // NORTH
        {
            var center = Map.GridToWorld((int)(Map.Width / 2f - 0.5f), Map.Height - 1, 0);

            SpawnBorder(
                "Border_North",
                center,
                new Vector3(scaleX_Long, scaleY_Thin, scaleZ)
            );
        }

        // WEST
        {
            var center = Map.GridToWorld(0, (int)(Map.Height / 2 - 0.5), 0);

            SpawnBorder(
                "Border_West",
                center,
                new Vector3(scaleX_Thin, scaleY_Long, scaleZ)
            );
        }

        // EAST
        {
            var center = Map.GridToWorld(Map.Width - 1, (int)(Map.Height / 2 - 0.5), 0);

            SpawnBorder(
                "Border_East",
                center,
                new Vector3(scaleX_Thin, scaleY_Long, scaleZ)
            );
        }
    }

    /// <summary>
    /// Spawnaa yksittäisen ulkorajan seinän annetulla nimellä, sijainnilla ja skaalalla.
    /// </summary>
    public void SpawnBorder(string name, Vector3 center, Vector3 scale)
    {
        var go = new GameObject(name);

        var renderer = go.Components.Create<ModelRenderer>();
        renderer.Model = BoxModel;
        renderer.MaterialOverride = WallMaterialOuter;

        go.Transform.Scale = scale;

        float worldHeight = BoxModel.Bounds.Size.z * scale.z;

        go.Transform.Position = new Vector3(
            center.x,
            center.y,
            FloorZ + worldHeight * 0.5f
        );

        var collider = go.Components.Create<BoxCollider>();
        collider.Static = true;

        go.Tags.Add("solid");

        // 🔥 TÄRKEÄ OSA — OIKEIN SÄÄDETTY TILING

        float tilesX = Map.Width / BorderUVFactor;
        float tilesY = 1;

        WallMaterialOuter.Set("g_vTexCoordScale",
            new Vector2(tilesX, tilesY));
    }

    /// <summary>
    /// Rakentaa talo-id:t flood fill -algoritmilla.
    /// Jokainen yhtenäinen seinä-alue ('-') saa oman id:n ja materiaalin.
    /// </summary>
    public void BuildHouseIds()
    {
        owner.HouseIds = new int[Map.Width, Map.Height];
        int nextHouseId = 1;

        for (int y = 0; y < Map.Height; y++)
        {
            for (int x = 0; x < Map.Width; x++)
            {
                if (HouseIds[x, y] != 0)
                    continue;

                // talon seinä
                if (Map.Cells[x, y] != '-')
                    continue;

                FloodFillHouse(x, y, nextHouseId);
                nextHouseId++;
            }
        }

        // arvo materiaali per talo
        for (int id = 1; id < nextHouseId; id++)
        {
            int matIndex = Game.Random.Int(1, owner.WallMaterials.Count - 1);
            HouseWallMaterials[id] = Material.Load(owner.WallMaterials[matIndex]);
        }
    }

    /// <summary>
    /// Flood fill -algoritmi talo-id:n levittämiseksi yhtenäiselle seinäalueelle.
    /// </summary>
    public void FloodFillHouse(int startX, int startY, int houseId)
    {
        var stack = new Stack<(int x, int y)>();
        stack.Push((startX, startY));

        while (stack.Count > 0)
        {
            var (x, y) = stack.Pop();

            if (!Map.InBounds(x, y))
                continue;

            if (HouseIds[x, y] != 0)
                continue;

            if (Map.Cells[x, y] != '-')
                continue;

            HouseIds[x, y] = houseId;

            stack.Push((x + 1, y));
            stack.Push((x - 1, y));
            stack.Push((x, y + 1));
            stack.Push((x, y - 1));
        }
    }
}