2024-10-30 19:01:58 +03:00

244 lines
6.2 KiB
C#

using SWB.Shared;
using System;
namespace SWB.Base.Attachments;
public enum AttachmentCategory
{
Barrel,
Sight,
Grip,
Rail,
Magazine,
Muzzle,
Stock,
Other,
Special,
Tactical,
Laser,
None,
}
/*
* Attachment base that allows for weapon bone parenting as well as bodygroup changes simultaneously
*/
[Group( "SWB Attachments" )]
public abstract class Attachment : Component, IComparable<Attachment>
{
/// <summary>Display name (needs to be unique)</summary>
public virtual string Name => "";
/// <summary>Display description</summary>
public virtual string Description => "";
/// <summary>Only 1 active attachment per category, this is also used to determine what effect attachment to overide</summary>
public virtual AttachmentCategory Category => AttachmentCategory.None;
/// <summary>List of positive attributes</summary>
public virtual string[] Positives => Array.Empty<string>();
/// <summary>List of negative attributes</summary>
public virtual string[] Negatives => Array.Empty<string>();
/// <summary>Weapon stats changer</summary>
public virtual StatsModifier StatsModifier { get; set; }
/// <summary>Path to an image that represent the attachment on the HUD</summary>
public virtual string IconPath => "";
/// <summary>Path to the attachment model</summary>
public virtual string ModelPath => "";
/// <summary>Name of the model attachment used for new effect origins</summary>
public virtual string EffectAttachmentOrBone { get; set; } = "";
/// <summary>Hide this attachment in menus</summary>
public virtual bool Hide { get; set; } = false;
/// <summary>Depends on another attachment (e.g. rail/mount)</summary>
[Property] public Attachment RequiresAttachment { get; set; }
/// <summary>Name of the bone you want to attach the model to</summary>
[Property, Group( "Model Parenting" )] public virtual string Bone { get; set; }
/// <summary>Viewmodel scale</summary>
[Property, Group( "Model Parenting" )] public virtual Vector3 ViewModelScale { get; set; } = Vector3.One;
/// <summary>Worldmodel scale</summary>
[Property, Group( "Model Parenting" )] public virtual Vector3 WorldModelScale { get; set; } = Vector3.One;
/// <summary>The name of the body group</summary>
[Property, Group( "BodyGroup" )] public virtual string BodyGroup { get; set; }
/// <summary>The name of the body group choice</summary>
[Property, Group( "BodyGroup" )] public virtual int BodyGroupChoice { get; set; } = 0;
/// <summary>The default target body group value</summary>
[Property, Group( "BodyGroup" )] public virtual int BodyGroupDefault { get; set; } = 0;
/// <summary>If already equipped</summary>
[Sync] public bool Equipped { get; private set; }
public Weapon Weapon { get; private set; }
public SkinnedModelRenderer ViewModelRenderer { get; private set; }
public SkinnedModelRenderer WorldModelRenderer { get; private set; }
private int equipTries = 0;
private bool equippedOnClient = false;
protected override void OnAwake()
{
Weapon = Components.Get<Weapon>();
}
private void SetBodyGroup( int choice )
{
if ( string.IsNullOrEmpty( BodyGroup ) ) return;
Weapon.WorldModelRenderer.SetBodyGroup( BodyGroup, choice );
}
private void CreateModel( bool isViewModel = false )
{
if ( string.IsNullOrEmpty( ModelPath ) || string.IsNullOrEmpty( Bone ) ) return;
var attachmentGo = new GameObject( true, "Attachment" );
attachmentGo.Tags.Add( TagsHelper.Attachment );
var attachmentRenderer = attachmentGo.Components.Create<SkinnedModelRenderer>();
attachmentRenderer.Model = Model.Load( ModelPath );
attachmentRenderer.Enabled = true;
attachmentRenderer.WorldScale = WorldModelScale;
WorldModelRenderer = attachmentRenderer;
ModelUtil.ParentToBone( attachmentGo, Weapon.WorldModelRenderer, Bone );
}
private void CreateModels()
{
if ( !IsProxy )
CreateModel( true );
CreateModel();
}
/// <summary>Equips the attachment for everyone</summary>
[Broadcast]
public virtual void EquipBroadCast()
{
if ( !IsValid ) return;
Equip();
}
/// <summary>Equips the attachment</summary>
public virtual void Equip()
{
// Log.Info( "Trying to equip -> " + Name + ", info -> equippedOnClient: " + equippedOnClient + " equipTries: " + equipTries );
if ( equippedOnClient || !IsValid || Weapon is null ) return;
if ( !IsProxy || Weapon.WorldModelRenderer is null )
{
if ( equipTries > 10 ) return;
equipTries += 1;
async void retry()
{
await GameTask.Delay( 1 );
Equip();
}
retry();
return;
}
// Make sure there is no other active attachment in same category
foreach ( var att in Weapon.Attachments )
{
if ( att.Category == Category && att.Equipped )
{
att.Unequip();
break;
}
}
equippedOnClient = true;
if ( !IsProxy )
Equipped = true;
// Equip dependent attachment
RequiresAttachment?.Equip();
// BodyGroup
SetBodyGroup( BodyGroupChoice );
// Models
CreateModels();
// Stats
StatsModifier?.Apply( Weapon );
if ( !IsProxy )
CreateHudElements();
OnEquip();
}
/// <summary>Unequips the attachment for everyone</summary>
[Broadcast]
public virtual void UnEquipBroadCast()
{
if ( !IsValid ) return;
Unequip();
}
/// <summary>Unequips the attachment</summary>
public virtual void Unequip()
{
if ( !equippedOnClient ) return;
equippedOnClient = false;
if ( !IsProxy )
Equipped = false;
// Unequip dependent attachment
RequiresAttachment?.Unequip();
// BodyGroup
SetBodyGroup( BodyGroupDefault );
// Model
WorldModelRenderer?.GameObject.Destroy();
// Stats
StatsModifier?.Remove( Weapon );
if ( !IsProxy )
DestroyHudElements();
OnUnequip();
}
/// <summary>Gets called after the attachment is equipped</summary>
public abstract void OnEquip();
/// <summary>Gets called after the attachment is unequipped</summary>
public abstract void OnUnequip();
/// <summary>Gets called when the weapon is creating its HUD elements</summary>
public virtual void CreateHudElements() { }
/// <summary>Gets called when the weapon is destroying its HUD elements</summary>
public virtual void DestroyHudElements() { }
public int CompareTo( Attachment obj )
{
if ( obj == null )
return 1;
else
return Name.CompareTo( obj.Name );
}
}