swb_base/BulletBase.HitScan.cs
using Sandbox;
using Sandbox.Sboku;
using SWB.Demo;
using SWB.Shared;
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;

namespace SWB.Base;

public class HitScanBullet : IBulletBase
{
    public void Shoot(Weapon weapon, ShootInfo shootInfo, Vector3 spreadOffset)
    {
        if (!weapon.IsValid) return;

        var player = weapon.Owner;
        if (player is null) return;

        var forward = (player.EyeAngles.Forward + spreadOffset).Normal;
        var endPos = player.EyePos + forward * 999999f;

        var bulletTr = weapon.TraceBullet(player.EyePos, endPos);
        if (SurfaceUtil.IsSkybox(bulletTr.Surface) || bulletTr.HitPosition == Vector3.Zero)
            return;

        var hitObj = bulletTr.GameObject;

        // Impact + tracer (kuten ennen)
        if (!weapon.IsProxy)
        {
            weapon.CreateBulletImpact(bulletTr);

            if (shootInfo.BulletTracerParticle is not null)
            {
                if (Random.Shared.NextDouble() < shootInfo.BulletTracerChance)
                    TracerEffects(weapon, shootInfo, bulletTr.HitPosition);
            }
        }

        // =========================
        // 1️⃣ SEINÄT (WallRoot)
        // =========================
        /*
         * Hostilla

ApplyDamage() → Networking.IsHost == true

→ kutsuu suoraan GridSpawner.DestroyWall(x, y)

→ seinä hajoaa + FX

Clientilla

ApplyDamage() → Networking.IsHost == false

→ kutsuu RpcRequestDestroyWallWorld(hitPos)

→ host laskee gridin ja kutsuu DestroyWall

→ kaikki synkkaa
         * */
        /// 🔥 1) SEINÄT / RAKENTEET

        // 1️⃣ Segmentti ensin (jos pylväs on jo segmentoitunut)
        if (hitObj.Components.TryGet<WallSegmentDamage>(out var seg))
        {
            seg.RpcHit(25, bulletTr.EndPosition);
            return;
        }

        // 2️⃣ Muuten koko pylväs
        if (hitObj.Components.TryGet<WallColumnDamage>(out var wall))
        {
            wall.RpcHit(25, bulletTr.EndPosition);
            return;
        }

        // 3️⃣ PRISON CONCRETE BLOCKS
        if (hitObj.Components.TryGet<PrisonBreakableBlock>(out var prisonBlock))
        {
            prisonBlock.RpcHit(25, bulletTr.EndPosition);
            return;
        }

        // =========================
        // 4️⃣ VANHA PALIKKAMALLI (fallback)
        // =========================
        /*if (hitObj?.Components.TryGet<DisappearOnHit>(out var disappear) == true)
        {
            if (Networking.IsHost)
                disappear.Hit(10);
        }*/

        // =========================
        // 3️⃣ PELAAJAT / BOTIT (VANHA LOGIIKKA)
        // =========================
        if(hitObj is not null && hitObj.Tags.Has(TagsHelper.Player))
{
            Log.Info($"Hit tags: {string.Join(",", hitObj.Tags)}");
            var target = hitObj.Components.GetInAncestorsOrSelf<IPlayerBase>();
            if (!target?.IsAlive ?? true) return;

            var hitTags = Array.Empty<string>();
            if (bulletTr.Hitbox is not null)
                hitTags = bulletTr.Hitbox.Tags.TryGetAll().ToArray();

            RpcApplyBulletDamage(
                weapon.Owner.GameObject.Id,
                target.Id,
                shootInfo.Damage,
                bulletTr.HitPosition,
                forward * 100 * shootInfo.Force,
                hitTags
            );
        }

    }



    void SpawnBulletHole(SceneTraceResult tr)
    {
        

    }






    [Rpc.Host]
    private static void RpcApplyBulletDamage(
    Guid attackerId,
    Guid targetId,
    float damage,
    Vector3 hitPos,
    Vector3 force,
    string[] hitTags
)
    {
       
            foreach (var player in Game.ActiveScene.GetAllComponents<IPlayerBase>())
            {
                //Log.Info($"Player WAS HIT: id={player.GameObject.Id} by attackerId:{attackerId}");
                //Log.Info($"TargetId {targetId}");
                if (player.Id == targetId)
                {

                    

                    var dmgInfo = Shared.DamageInfo.FromBullet(
                        attackerId,
                        "",
                        damage,
                        hitPos,
                        force,
                        hitTags
                    );
                    //Log.Info($"Applying damage: target={targetId}");
                    player.TakeDamage(dmgInfo);
                break;
                }
            }
      
        
    }


    public Vector3 GetRandomSpread( float spread )
	{
		return (Vector3.Random + Vector3.Random + Vector3.Random + Vector3.Random) * spread * 0.25f;
	}

	void TracerEffects( Weapon weapon, ShootInfo shootInfo, Vector3 endPos )
	{
		var scale = weapon.CanSeeViewModel ? shootInfo.VMParticleScale : shootInfo.WMParticleScale;
		var muzzleTransform = weapon.GetMuzzleTransform();

		if ( !muzzleTransform.HasValue ) return;

		SceneParticles particles = new( weapon.Scene.SceneWorld, shootInfo.BulletTracerParticle );
		particles?.SetControlPoint( 1, muzzleTransform.Value );
		particles?.SetControlPoint( 2, endPos );
		particles?.SetNamedValue( "scale", scale );
		particles?.PlayUntilFinished( TaskSource.Create() );
	}
}