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 { /// Display name (needs to be unique) public virtual string Name => ""; /// Display description public virtual string Description => ""; /// Only 1 active attachment per category, this is also used to determine what effect attachment to overide public virtual AttachmentCategory Category => AttachmentCategory.None; /// List of positive attributes public virtual string[] Positives => Array.Empty(); /// List of negative attributes public virtual string[] Negatives => Array.Empty(); /// Weapon stats changer public virtual StatsModifier StatsModifier { get; set; } /// Path to an image that represent the attachment on the HUD public virtual string IconPath => ""; /// Path to the attachment model public virtual string ModelPath => ""; /// Name of the model attachment used for new effect origins public virtual string EffectAttachmentOrBone { get; set; } = ""; /// Hide this attachment in menus public virtual bool Hide { get; set; } = false; /// Depends on another attachment (e.g. rail/mount) [Property] public Attachment RequiresAttachment { get; set; } /// Name of the bone you want to attach the model to [Property, Group( "Model Parenting" )] public virtual string Bone { get; set; } /// Viewmodel scale [Property, Group( "Model Parenting" )] public virtual Vector3 ViewModelScale { get; set; } = Vector3.One; /// Worldmodel scale [Property, Group( "Model Parenting" )] public virtual Vector3 WorldModelScale { get; set; } = Vector3.One; /// The name of the body group [Property, Group( "BodyGroup" )] public virtual string BodyGroup { get; set; } /// The name of the body group choice [Property, Group( "BodyGroup" )] public virtual int BodyGroupChoice { get; set; } = 0; /// The default target body group value [Property, Group( "BodyGroup" )] public virtual int BodyGroupDefault { get; set; } = 0; /// If already equipped [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(); } 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(); 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(); } /// Equips the attachment for everyone [Broadcast] public virtual void EquipBroadCast() { if ( !IsValid ) return; Equip(); } /// Equips the attachment 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(); } /// Unequips the attachment for everyone [Broadcast] public virtual void UnEquipBroadCast() { if ( !IsValid ) return; Unequip(); } /// Unequips the attachment 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(); } /// Gets called after the attachment is equipped public abstract void OnEquip(); /// Gets called after the attachment is unequipped public abstract void OnUnequip(); /// Gets called when the weapon is creating its HUD elements public virtual void CreateHudElements() { } /// Gets called when the weapon is destroying its HUD elements public virtual void DestroyHudElements() { } public int CompareTo( Attachment obj ) { if ( obj == null ) return 1; else return Name.CompareTo( obj.Name ); } }