using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Weapons.Melee; /// /// When given to a mob lets them do unarmed attacks, or when given to an item lets someone wield it to do attacks. /// [RegisterComponent, NetworkedComponent] public sealed class MeleeWeaponComponent : Component { /// /// Should the melee weapon's damage stats be examinable. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("hidden")] public bool HideFromExamine { get; set; } = false; /// /// Next time this component is allowed to light attack. Heavy attacks are wound up and never have a cooldown. /// [ViewVariables(VVAccess.ReadWrite), DataField("nextAttack", customTypeSerializer:typeof(TimeOffsetSerializer))] public TimeSpan NextAttack; /// /// Starts attack cooldown when equipped if true. /// [ViewVariables(VVAccess.ReadWrite), DataField("resetOnHandSelected")] public bool ResetOnHandSelected = true; /* * Melee combat works based around 2 types of attacks: * 1. Click attacks with left-click. This attacks whatever is under your mnouse * 2. Wide attacks with right-click + left-click. This attacks whatever is in the direction of your mouse. */ /// /// How many times we can attack per second. /// [ViewVariables(VVAccess.ReadWrite), DataField("attackRate")] public float AttackRate = 1f; /// /// Are we currently holding down the mouse for an attack. /// Used so we can't just hold the mouse button and attack constantly. /// [ViewVariables(VVAccess.ReadWrite)] public bool Attacking = false; /// /// When did we start a heavy attack. /// /// [ViewVariables(VVAccess.ReadWrite), DataField("windUpStart")] public TimeSpan? WindUpStart; /// /// How long it takes a heavy attack to windup. /// [ViewVariables] public TimeSpan WindupTime => AttackRate > 0 ? TimeSpan.FromSeconds(1 / AttackRate * HeavyWindupModifier) : TimeSpan.Zero; /// /// Heavy attack windup time gets multiplied by this value and the light attack cooldown. /// [ViewVariables(VVAccess.ReadWrite), DataField("heavyWindupModifier")] public float HeavyWindupModifier = 1.5f; /// /// Light attacks get multiplied by this over the base value. /// [ViewVariables(VVAccess.ReadWrite), DataField("heavyDamageModifier")] public FixedPoint2 HeavyDamageModifier = FixedPoint2.New(2); /// /// Base damage for this weapon. Can be modified via heavy damage or other means. /// [DataField("damage", required:true)] [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier Damage = default!; [DataField("bluntStaminaDamageFactor")] [ViewVariables(VVAccess.ReadWrite)] public FixedPoint2 BluntStaminaDamageFactor { get; set; } = 0.5f; // TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2 /// /// Nearest edge range to hit an entity. /// [ViewVariables(VVAccess.ReadWrite), DataField("range")] public float Range = 1.5f; /// /// Total width of the angle for wide attacks. /// [ViewVariables(VVAccess.ReadWrite), DataField("angle")] public Angle Angle = Angle.FromDegrees(60); [ViewVariables(VVAccess.ReadWrite), DataField("animation", customTypeSerializer: typeof(PrototypeIdSerializer))] public string ClickAnimation = "WeaponArcPunch"; [ViewVariables(VVAccess.ReadWrite), DataField("wideAnimation", customTypeSerializer: typeof(PrototypeIdSerializer))] public string WideAnimation = "WeaponArcSlash"; // Sounds /// /// This gets played whenever a melee attack is done. This is predicted by the client. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("soundSwing")] public SoundSpecifier SwingSound { get; set; } = new SoundPathSpecifier("/Audio/Weapons/punchmiss.ogg") { Params = AudioParams.Default.WithVolume(-3f).WithVariation(0.025f), }; // We do not predict the below sounds in case the client thinks but the server disagrees. If this were the case // then a player may doubt if the target actually took damage or not. // If overwatch and apex do this then we probably should too. [ViewVariables(VVAccess.ReadWrite)] [DataField("soundHit")] public SoundSpecifier? HitSound; /// /// Plays if no damage is done to the target entity. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("soundNoDamage")] public SoundSpecifier NoDamageSound { get; set; } = new SoundPathSpecifier("/Audio/Weapons/tap.ogg"); } /// /// Event raised on entity in GetWeapon function to allow systems to manually /// specify what the weapon should be. /// public sealed class GetMeleeWeaponEvent : HandledEntityEventArgs { public EntityUid? Weapon; } [Serializable, NetSerializable] public sealed class MeleeWeaponComponentState : ComponentState { // None of the other data matters for client as they're not predicted. public float AttackRate; public bool Attacking; public TimeSpan NextAttack; public TimeSpan? WindUpStart; public string ClickAnimation; public string WideAnimation; public float Range; public MeleeWeaponComponentState(float attackRate, bool attacking, TimeSpan nextAttack, TimeSpan? windupStart, string clickAnimation, string wideAnimation, float range) { AttackRate = attackRate; Attacking = attacking; NextAttack = nextAttack; WindUpStart = windupStart; ClickAnimation = clickAnimation; WideAnimation = wideAnimation; Range = range; } }