Weapons/BaseCarryable/Carryable.cs
using Sandbox.Rendering;
/// <summary>
/// Info about a trace attack. It's a struct so we can add to it without updating params everywhere.
/// </summary>
/// <param name="Target"></param>
/// <param name="Damage"></param>
/// <param name="Tags"></param>
/// <param name="Position"></param>
/// <param name="Origin"></param>
public record struct TraceAttackInfo( GameObject Target, float Damage, TagSet Tags = null, Vector3 Position = default, Vector3 Origin = default )
{
/// <summary>
/// Constructs a <see cref="TraceAttackInfo"/> from a trace and input damage.
/// </summary>
public static TraceAttackInfo From( SceneTraceResult tr, float damage, TagSet tags = default, bool localise = true )
{
tags ??= new();
if ( localise && tr.Hitbox?.Tags is not null )
{
tags.Add( tr.Hitbox?.Tags );
}
return new TraceAttackInfo( tr.GameObject, damage, tags, tr.HitPosition, tr.StartPosition );
}
}
/// <summary>
/// A carryable is an item that can be carried in a player's inventory, such as a weapon or tool. This can be selected and equipped by the player.
/// The most notable difference between an item and carryable is that carryables have viewmodels and world models and can be selected in the weapon list.
/// </summary>
public partial class Carryable : Item, IKillIcon
{
[Property, Feature( "Inventory" ), Range( 0, 4 )]
public int InventorySlot { get; set; } = 0;
[Property, Feature( "Inventory" )]
public int InventoryOrder { get; set; } = 0;
[Property, Feature( "Inventory" ), TextArea]
public Texture DisplayIcon { get; set; }
public GameObject ViewModel { get; protected set; }
public GameObject WorldModel { get; protected set; }
/// <summary>
/// Wether this weapon should be avoided when determining an item to swap to
/// </summary>
public virtual bool ShouldAvoid => false;
/// <summary>
/// The value of this weapon, used for auto-switch.
/// </summary>
[Property, Feature( "Inventory" )]
public int Value { get; set; } = 0;
/// <summary>
/// Gets a reference to the weapon model for this weapon - if there's a viewmodel, pick the viewmodel, if not, world model.
/// </summary>
public WeaponModel WeaponModel
{
get
{
var go = ViewModel;
if ( !go.IsValid() ) go = WorldModel;
if ( !go.IsValid() ) return null;
return go.GetComponent<WeaponModel>();
}
}
/// <summary>
/// Can we switch to this?
/// </summary>
/// <returns></returns>
public virtual bool CanSwitch()
{
return true;
}
protected override void OnEnabled()
{
CreateWorldModel();
}
protected override void OnDisabled()
{
DestroyWorldModel();
DestroyViewModel();
}
protected override void OnUpdate()
{
var player = Owner;
var controller = player?.Controller;
if ( controller is null ) return;
controller.Renderer.Set( "holdtype", (int)HoldType );
if ( player.IsLocalPlayer )
{
if ( Scene.Camera is null )
return;
var hud = Scene.Camera.Hud;
var aimPos = Screen.Size * 0.5f;
if ( controller.ThirdPerson )
{
var tr = Scene.Trace.Ray( controller.EyeTransform.ForwardRay, 4096 )
.IgnoreGameObjectHierarchy( controller.GameObject )
.Run();
aimPos = Scene.Camera.PointToScreenPixels( tr.EndPosition );
}
if ( UI.IsEnabled )
{
DrawHud( hud, aimPos );
}
}
}
public virtual void DrawHud( HudPainter painter, Vector2 crosshair )
{
// nothing
}
/// <summary>
/// Called when this is pulled out
/// </summary>
public virtual void OnEquipped( Player player )
{
}
/// <summary>
/// Called when this is put away
/// </summary>
public virtual void OnHolstered( Player player )
{
}
public virtual void OnPlayerUpdate( Player player )
{
if ( player is null ) return;
if ( !player.Controller.ThirdPerson )
{
CreateViewModel();
}
else
{
DestroyViewModel();
}
GameObject.Network.Interpolation = false;
if ( IsProxy )
return;
OnControl( player );
}
/// <summary>
/// Called every update, scoped to the owning player
/// </summary>
/// <param name="player"></param>
public virtual void OnControl( Player player )
{
}
/// <summary>
/// Called when setting up the camera - use this to apply effects on the camera based on this carriable
/// </summary>
/// <param name="player"></param>
/// <param name="camera"></param>
public virtual void OnCameraSetup( Player player, Sandbox.CameraComponent camera )
{
}
/// <summary>
/// Can directly influence the player's eye angles here
/// </summary>
/// <param name="player"></param>
/// <param name="angles"></param>
public virtual void OnCameraMove( Player player, ref Angles angles )
{
}
/// <summary>
/// Run a trace related attack with some set information.
/// This is targeted to the host who then does things.
/// </summary>
/// <param name="attack"></param>
[Rpc.Host]
public void TraceAttack( TraceAttackInfo attack )
{
if ( !attack.Target.IsValid() )
return;
if ( !Owner.IsValid() )
return;
var damagable = attack.Target.GetComponentInParent<IDamageable>();
if ( damagable is not null )
{
var info = new DamageInfo( attack.Damage, Owner.GameObject, GameObject );
info.Position = attack.Position;
info.Origin = attack.Origin;
info.Tags = attack.Tags;
damagable.OnDamage( info );
}
if ( attack.Target.GetComponentInChildren<Rigidbody>() is var rb && rb.IsValid() )
{
rb.ApplyForce( (attack.Position - attack.Origin) * 1000f );
}
}
/// <summary>
/// Tell the player to switch away from us, to the next best weapon
/// </summary>
public void SwitchAway()
{
var player = Owner;
if ( !player.IsValid() ) return;
var inventory = player.GetComponent<PlayerInventory>();
if ( !inventory.IsValid() ) return;
var candidate = inventory.GetBestWeaponHolstered();
if ( !candidate.IsValid() )
return;
inventory.SwitchWeapon( candidate );
}
/// <summary>
/// Is this item currently being used? When true, prevents auto-switching away on item pickup etc.
/// </summary>
public virtual bool IsInUse()
{
return false;
}
}