SausageExplosiveBolt.cs
using Sandbox;
using System;
namespace PaintballBaddies;
// Replicated fuse and bone attachment. Only the host resolves the struck part;
// unlike the grenade this never deals area damage to neighbouring enemies.
public sealed class SausageExplosiveBolt : Component
{
[Sync(SyncFlags.FromHost)] public GameObject Enemy {get;set;}
[Sync(SyncFlags.FromHost)] public GameObject Shooter {get;set;}
[Sync(SyncFlags.FromHost)] public string Bone {get;set;}
[Sync(SyncFlags.FromHost)] public Vector3 BonePoint {get;set;}
[Sync(SyncFlags.FromHost)] public Rotation BoneRotation {get;set;}
[Sync(SyncFlags.FromHost)] public int Part {get;set;}
[Sync(SyncFlags.FromHost)] public int Round {get;set;}
[Sync(SyncFlags.FromHost)] public float Fuse {get;set;}=2;
private GameObject visual;
private float beep;
private bool resolved;
public static int Detonations {get;private set;}
public static void Attach(OverdriveEnemy enemy,PaintballCombatant shooter,Vector3 point,Vector3 direction,string bone)
{
if(!MultiplayerSession.Authority || enemy.Dead)return;
int part=enemy.Zone(point,bone);
if(string.IsNullOrEmpty(bone))bone=part switch {1=>"head",2=>"arm_upper_L",4=>"arm_upper_R",8=>"leg_upper_L",16=>"leg_upper_R",_=>"spine_1"};
var pose=enemy.Components.Get<OverdriveRobotRig>().IntactPose(bone);
var go=new GameObject(false,"Embedded explosive bolt"){WorldPosition=point};go.Tags.Add("paintball_debris");
var bolt=go.Components.Create<SausageExplosiveBolt>();bolt.Enemy=enemy.GameObject;bolt.Shooter=shooter?.GameObject;
bolt.Bone=bone;bolt.Part=part;bolt.Round=enemy.Round;
bolt.BonePoint=pose.PointToLocal(point+direction*2);bolt.BoneRotation=pose.Rotation.Inverse*Rotation.LookAt(direction);
go.Enabled=true;
if(MultiplayerSession.Online){go.NetworkMode=NetworkMode.Object;go.Network.SetOwnerTransfer(OwnerTransfer.Fixed);go.NetworkSpawn();}
}
protected override void OnStart(){visual=SausageBolt.Visual(GameObject);}
private Transform Pose(OverdriveEnemy enemy)
{
var rig=enemy.Components.Get<OverdriveRobotRig>();
var pose=rig.IsValid() ? rig.IntactPose(Bone) : enemy.WorldTransform;
if(enemy.Dead && Scene.GetAllComponents<OverdriveCorpse>().FirstOrDefault(c=>c.SourceEnemy==Enemy)?.VisibleBody is {} corpse
&& corpse.IsValid() && corpse.TryGetBoneTransform(Bone,out var fallen))pose=fallen;
return pose;
}
protected override void OnUpdate()
{
var enemy=Enemy.IsValid() ? Enemy.Components.Get<OverdriveEnemy>() : null;
var s=MultiplayerSession.Find(Scene);
if(!enemy.IsValid() || s is not {IsOverdrive:true,InRound:true} || s.RoundId!=Round)
{if(MultiplayerSession.Authority)GameObject.Destroy();return;}
if((enemy.MissingParts&Part)!=0){if(MultiplayerSession.Authority)GameObject.Destroy();return;}
var pose=Pose(enemy);
if(MultiplayerSession.Authority)
{
WorldPosition=pose.PointToWorld(BonePoint);WorldRotation=pose.Rotation*BoneRotation;
Fuse=MathF.Max(0,Fuse-Time.Delta);
if(Fuse<=0 && !resolved)
{
resolved=true;Detonations++;
var shooter=Shooter.IsValid() ? Shooter.Components.Get<PaintballCombatant>() : null;
bool hit=false;
if(!enemy.Dead)
{
hit=enemy.ExplodeBoltPart(Part,WorldRotation.Forward,shooter,WorldPosition);
}
Explosion(WorldPosition,Round);
if(hit && shooter.IsValid())HitConfirm.Send(shooter.GameObject,enemy.Dead,WorldPosition,Round);
GameObject.Destroy();return;
}
}
beep-=Time.Delta;
if(beep<=0 && Fuse>0)
{
var sound=Sound.Play("sounds/paintball/countdown_tick.sound",pose.PointToWorld(BonePoint));
if(sound is not null){sound.Volume=.3f;sound.Pitch=1.35f;}
beep=Fuse>1 ? .65f : .24f;
}
}
protected override void OnPreRender()
{
var enemy=Enemy.IsValid() ? Enemy.Components.Get<OverdriveEnemy>() : null;
if(!enemy.IsValid() || !visual.IsValid())return;
visual.Enabled=(enemy.MissingParts&Part)==0;
var pose=Pose(enemy);var rotation=pose.Rotation*BoneRotation;
visual.WorldRotation=rotation;
visual.WorldPosition=pose.PointToWorld(BonePoint)+rotation*new Vector3(-28.4036f*.60f,0,0);
}
[Rpc.Broadcast(NetFlags.HostOnly)] private static async void Explosion(Vector3 point,int round)
{
var scene=Game.ActiveScene;await GameTask.MainThread();
if(!scene.IsValid() || scene!=Game.ActiveScene || MultiplayerSession.Find(scene) is not {IsOverdrive:true} s || s.RoundId!=round)return;
SausageExplosionEffect.Spawn(scene,point,false);
}
}