PropSpawner.cs

Component that spawns props (barrel, tar, anvil, spring). It exposes prefab and cost properties, checks score via ScoreManager, computes a spawn position using the scene camera and a trace, then clones the selected prefab and deducts cost on success.

File Access
using Sandbox;
using System.Linq;

public sealed class PropSpawner : Component
{
    public static PropSpawner Instance { get; private set; }

    [Property] public GameObject BarrelPrefab { get; set; }
    [Property] public GameObject TarPrefab { get; set; }
    [Property] public GameObject AnvilPrefab { get; set; }
    [Property] public GameObject SpringPrefab { get; set; }

    [Property] public int BarrelCost { get; set; } = 15;
    [Property] public int TarCost { get; set; } = 20;
    [Property] public int AnvilCost { get; set; } = 25;
    [Property] public int SpringCost { get; set; } = 20;

    [Property] public float MaxSpawnDistance { get; set; } = 500f;

    protected override void OnAwake()
    {
        Instance = this;
    }

    public bool SpawnByIndex( int index )
    {
        return index switch
        {
            0 => BuyAndSpawn( BarrelPrefab, BarrelCost ),
            1 => BuyAndSpawn( TarPrefab, TarCost ),
            2 => BuyAndSpawn( AnvilPrefab, AnvilCost ),
            3 => BuyAndSpawn( SpringPrefab, SpringCost ),
            _ => false
        };
    }

    public bool BuyAndSpawn( GameObject prefab, int cost )
    {
        if ( !prefab.IsValid() )
        {
            Log.Warning( "[PropSpawner] Ошибка: Префаб не назначен в Inspector!" );
            return false;
        }

        int currentScore = ScoreManager.Instance?.Score ?? 999; // Если ScoreManager нет — даем спавнить для теста
        if ( currentScore < cost )
        {
            Log.Warning( $"[PropSpawner] Недостаточно очков! Нужно: {cost}, есть: {currentScore}" );
            return false;
        }

        Vector3 spawnPos = CalculateSpawnPosition();

        var spawnedObj = prefab.Clone( spawnPos );
        if ( spawnedObj.IsValid() )
        {
           if ( ScoreManager.Instance != null )
{
    ScoreManager.Instance.AddScore( -cost );
}
            Log.Info( $"[PropSpawner] Заспавнен {prefab.Name} в позиции {spawnPos}" );
            return true;
        }

        return false;
    }

    private Vector3 CalculateSpawnPosition()
    {
        var cam = Scene.GetAllComponents<CameraComponent>().FirstOrDefault( c => !c.IsProxy );
        Vector3 startPos = cam.IsValid() ? cam.WorldPosition : WorldPosition;
        Vector3 forward = cam.IsValid() ? cam.WorldTransform.Forward : WorldTransform.Forward;

        // Смещаем точку старта луча на 60 единиц вперёд, чтобы не упираться в модель персонажа
        Vector3 rayStart = startPos + forward * 60f;
        Vector3 rayEnd = startPos + forward * MaxSpawnDistance;

        var ray = Scene.Trace.Ray( rayStart, rayEnd )
            .IgnoreGameObject( GameObject )
            .Run();

        if ( ray.Hit )
        {
            return ray.HitPosition + ray.Normal * 15f;
        }

        return rayStart + forward * 150f;
    }
}