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() );
}
}