diff --git a/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs b/Content.Server/_EE/Carrying/BeingCarriedComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs rename to Content.Server/_EE/Carrying/BeingCarriedComponent.cs diff --git a/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs b/Content.Server/_EE/Carrying/CarriableComponent.cs similarity index 58% rename from Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs rename to Content.Server/_EE/Carrying/CarriableComponent.cs index f4fd1fa6d56..eb12dbc904e 100644 --- a/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs +++ b/Content.Server/_EE/Carrying/CarriableComponent.cs @@ -9,9 +9,16 @@ public sealed partial class CarriableComponent : Component /// Number of free hands required /// to carry the entity /// - [DataField("freeHandsRequired")] + [DataField] public int FreeHandsRequired = 2; public CancellationTokenSource? CancelToken; + + /// + /// The base duration (In Seconds) of how long it should take to pick up this entity + /// before Contests are considered. + /// + [DataField] + public float PickupDuration = 3; } } diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs b/Content.Server/_EE/Carrying/CarryingComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs rename to Content.Server/_EE/Carrying/CarryingComponent.cs diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs b/Content.Server/_EE/Carrying/CarryingSystem.cs similarity index 71% rename from Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs rename to Content.Server/_EE/Carrying/CarryingSystem.cs index 322b0cdb0e0..55d74cb82e0 100644 --- a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs +++ b/Content.Server/_EE/Carrying/CarryingSystem.cs @@ -1,13 +1,10 @@ using System.Numerics; using System.Threading; using Content.Server.DoAfter; -using Content.Server.Body.Systems; -using Content.Server.Hands.Systems; using Content.Server.Resist; using Content.Server.Popups; using Content.Server.Inventory; using Content.Server.Nyanotrasen.Item.PseudoItem; -using Content.Shared.Climbing; // Shared instead of Server using Content.Shared.Mobs; using Content.Shared.DoAfter; using Content.Shared.Buckle.Components; @@ -16,30 +13,31 @@ using Content.Shared.Stunnable; using Content.Shared.Interaction.Events; using Content.Shared.Verbs; -using Content.Shared.Climbing.Events; // Added this. +using Content.Shared.Climbing.Events; using Content.Shared.Carrying; +using Content.Shared.Contests; using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; -using Content.Shared.Pulling; using Content.Shared.Standing; using Content.Shared.ActionBlocker; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Item; using Content.Shared.Throwing; -using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Mobs.Systems; using Content.Shared.Nyanotrasen.Item.PseudoItem; using Content.Shared.Storage; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; +using Robust.Server.GameObjects; namespace Content.Server.Carrying { public sealed class CarryingSystem : EntitySystem { - [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!; + [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!; [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly StandingStateSystem _standingState = default!; @@ -49,9 +47,9 @@ public sealed class CarryingSystem : EntitySystem [Dependency] private readonly EscapeInventorySystem _escapeInventorySystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; - [Dependency] private readonly RespiratorSystem _respirator = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly PseudoItemSystem _pseudoItem = default!; // Needed for fitting check + [Dependency] private readonly PseudoItemSystem _pseudoItem = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly TransformSystem _transform = default!; public override void Initialize() { @@ -78,22 +76,11 @@ public override void Initialize() private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent args) { - if (!args.CanInteract || !args.CanAccess) - return; - - if (!CanCarry(args.User, uid, component)) - return; - - if (HasComp(args.User)) // yeah not dealing with that - return; - - if (HasComp(args.User) || HasComp(args.Target)) - return; - - if (!_mobStateSystem.IsAlive(args.User)) - return; - - if (args.User == args.Target) + if (!args.CanInteract || !args.CanAccess || !_mobStateSystem.IsAlive(args.User) + || !CanCarry(args.User, uid, component) + || HasComp(args.User) + || HasComp(args.User) || HasComp(args.Target) + || args.User == args.Target) return; AlternativeVerb verb = new() @@ -113,13 +100,10 @@ private void AddInsertCarriedVerb(EntityUid uid, CarryingComponent component, Ge // If the person is carrying someone, and the carried person is a pseudo-item, and the target entity is a storage, // then add an action to insert the carried entity into the target var toInsert = args.Using; - if (toInsert is not { Valid: true } || !args.CanAccess || !TryComp(toInsert, out var pseudoItem)) - return; - - if (!TryComp(args.Target, out var storageComp)) - return; - - if (!_pseudoItem.CheckItemFits((toInsert.Value, pseudoItem), (args.Target, storageComp))) + if (toInsert is not { Valid: true } || !args.CanAccess + || !TryComp(toInsert, out var pseudoItem) + || !TryComp(args.Target, out var storageComp) + || !_pseudoItem.CheckItemFits((toInsert.Value, pseudoItem), (args.Target, storageComp))) return; InnateVerb verb = new() @@ -150,25 +134,22 @@ private void OnVirtualItemDeleted(EntityUid uid, CarryingComponent component, Vi /// Basically using virtual item passthrough to throw the carried person. A new age! /// Maybe other things besides throwing should use virt items like this... /// - private void OnThrow(EntityUid uid, CarryingComponent component, BeforeThrowEvent args) + private void OnThrow(EntityUid uid, CarryingComponent component, ref BeforeThrowEvent args) { - if (!TryComp(args.ItemUid, out var virtItem) || !HasComp(virtItem.BlockingEntity)) + if (!TryComp(args.ItemUid, out var virtItem) + || !HasComp(virtItem.BlockingEntity)) return; args.ItemUid = virtItem.BlockingEntity; - var multiplier = MassContest(uid, virtItem.BlockingEntity); - args.ThrowSpeed = 5f * multiplier; + args.ThrowSpeed *= _contests.MassContest(uid, virtItem.BlockingEntity, false, 2f) + * _contests.StaminaContest(uid, virtItem.BlockingEntity); } private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args) { var xform = Transform(uid); - if (xform.MapUid != args.OldMapId) - return; - - // Do not drop the carried entity if the new parent is a grid - if (xform.ParentUid == xform.GridUid) + if (xform.MapUid != args.OldMapId || xform.ParentUid == xform.GridUid) return; DropCarried(uid, component.Carried); @@ -198,16 +179,16 @@ private void OnInteractionAttempt(EntityUid uid, BeingCarriedComponent component /// private void OnMoveInput(EntityUid uid, BeingCarriedComponent component, ref MoveInputEvent args) { - if (!TryComp(uid, out var escape)) - return; - - if (!args.HasDirectionalMovement) + if (!TryComp(uid, out var escape) + || !args.HasDirectionalMovement) return; + // Check if the victim is in any way incapacitated, and if not make an escape attempt. + // Escape time scales with the inverse of a mass contest. Being lighter makes escape harder. if (_actionBlockerSystem.CanInteract(uid, component.Carrier)) { - // Note: the mass contest is inverted because weaker entities are supposed to take longer to escape - _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, MassContest(component.Carrier, uid)); + var disadvantage = _contests.MassContest(component.Carrier, uid, false, 2f); + _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, disadvantage); } } @@ -237,7 +218,7 @@ private void OnStartClimb(EntityUid uid, BeingCarriedComponent component, ref St DropCarried(component.Carrier, uid); } - private void OnBuckleChange(EntityUid uid, BeingCarriedComponent component, TEvent args) // Augh + private void OnBuckleChange(EntityUid uid, BeingCarriedComponent component, TEvent args) { DropCarried(component.Carrier, uid); } @@ -245,10 +226,8 @@ private void OnBuckleChange(EntityUid uid, BeingCarriedComponent compone private void OnDoAfter(EntityUid uid, CarriableComponent component, CarryDoAfterEvent args) { component.CancelToken = null; - if (args.Handled || args.Cancelled) - return; - - if (!CanCarry(args.Args.User, uid, component)) + if (args.Handled || args.Cancelled + || !CanCarry(args.Args.User, uid, component)) return; Carry(args.Args.User, uid); @@ -256,16 +235,18 @@ private void OnDoAfter(EntityUid uid, CarriableComponent component, CarryDoAfter } private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableComponent component) { - TimeSpan length = GetPickupDuration(carrier, carried); - - if (length >= TimeSpan.FromSeconds(9)) + if (!TryComp(carrier, out var carrierPhysics) + || !TryComp(carried, out var carriedPhysics) + || carriedPhysics.Mass > carrierPhysics.Mass * 2f) { _popupSystem.PopupEntity(Loc.GetString("carry-too-heavy"), carried, carrier, Shared.Popups.PopupType.SmallCaution); return; } - if (!HasComp(carried)) - length *= 2f; + var length = TimeSpan.FromSeconds(component.PickupDuration + * _contests.MassContest(carriedPhysics, carrierPhysics, false, 4f) + * _contests.StaminaContest(carrier, carried) + * (_standingState.IsDown(carried) ? 0.5f : 1)); component.CancelToken = new CancellationTokenSource(); @@ -287,12 +268,10 @@ private void Carry(EntityUid carrier, EntityUid carried) if (TryComp(carried, out var pullable)) _pullingSystem.TryStopPull(carried, pullable); - var carrierXform = Transform(carrier); - var xform = Transform(carried); - _transform.AttachToGridOrMap(carrier, carrierXform); - _transform.AttachToGridOrMap(carried, xform); - xform.Coordinates = carrierXform.Coordinates; - _transform.SetParent(carried, xform, carrier, carrierXform); + _transform.AttachToGridOrMap(carrier); + _transform.AttachToGridOrMap(carried); + _transform.SetCoordinates(carried, Transform(carrier).Coordinates); + _transform.SetParent(carried, carrier); _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier); _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier); @@ -309,17 +288,13 @@ private void Carry(EntityUid carrier, EntityUid carried) public bool TryCarry(EntityUid carrier, EntityUid toCarry, CarriableComponent? carriedComp = null) { - if (!Resolve(toCarry, ref carriedComp, false)) - return false; - - if (!CanCarry(carrier, toCarry, carriedComp)) - return false; - - // The second one means that carrier is a pseudo-item and is inside a bag. - if (HasComp(carrier) || HasComp(carrier)) - return false; - - if (GetPickupDuration(carrier, toCarry) > TimeSpan.FromSeconds(9)) + if (!Resolve(toCarry, ref carriedComp, false) + || !CanCarry(carrier, toCarry, carriedComp) + || HasComp(carrier) + || HasComp(carrier) + || TryComp(carrier, out var carrierPhysics) + && TryComp(toCarry, out var toCarryPhysics) + && carrierPhysics.Mass < toCarryPhysics.Mass * 2f) return false; Carry(carrier, toCarry); @@ -329,79 +304,42 @@ public bool TryCarry(EntityUid carrier, EntityUid toCarry, CarriableComponent? c public void DropCarried(EntityUid carrier, EntityUid carried) { - RemComp(carrier); // get rid of this first so we don't recusrively fire that event + RemComp(carrier); // get rid of this first so we don't recursively fire that event RemComp(carrier); RemComp(carried); RemComp(carried); _actionBlockerSystem.UpdateCanMove(carried); _virtualItemSystem.DeleteInHandsMatching(carrier, carried); - Transform(carried).AttachToGridOrMap(); + _transform.AttachToGridOrMap(carried); _standingState.Stand(carried); _movementSpeed.RefreshMovementSpeedModifiers(carrier); } private void ApplyCarrySlowdown(EntityUid carrier, EntityUid carried) { - var massRatio = MassContest(carrier, carried); - - if (massRatio == 0) - massRatio = 1; + var massRatio = _contests.MassContest(carrier, carried, true); + var massRatioSq = MathF.Pow(massRatio, 2); + var modifier = 1 - 0.15f / massRatioSq; + modifier = Math.Max(0.1f, modifier); - var massRatioSq = Math.Pow(massRatio, 2); - var modifier = (1 - (0.15 / massRatioSq)); - modifier = Math.Max(0.1, modifier); var slowdownComp = EnsureComp(carrier); - _slowdown.SetModifier(carrier, (float) modifier, (float) modifier, slowdownComp); + _slowdown.SetModifier(carrier, modifier, modifier, slowdownComp); } public bool CanCarry(EntityUid carrier, EntityUid carried, CarriableComponent? carriedComp = null) { - if (!Resolve(carried, ref carriedComp, false)) - return false; - - if (carriedComp.CancelToken != null) - return false; - - if (!HasComp(Transform(carrier).ParentUid)) - return false; - - if (HasComp(carrier) || HasComp(carried)) - return false; - - // if (_respirator.IsReceivingCPR(carried)) - // return false; - - if (!TryComp(carrier, out var hands)) - return false; - - if (hands.CountFreeHands() < carriedComp.FreeHandsRequired) + if (!Resolve(carried, ref carriedComp, false) + || carriedComp.CancelToken != null + || !HasComp(Transform(carrier).ParentUid) + || HasComp(carrier) + || HasComp(carried) + || !TryComp(carrier, out var hands) + || hands.CountFreeHands() < carriedComp.FreeHandsRequired) return false; return true; } - private float MassContest(EntityUid roller, EntityUid target, PhysicsComponent? rollerPhysics = null, PhysicsComponent? targetPhysics = null) - { - if (!Resolve(roller, ref rollerPhysics, false) || !Resolve(target, ref targetPhysics, false)) - return 1f; - - if (targetPhysics.FixturesMass == 0) - return 1f; - - return rollerPhysics.FixturesMass / targetPhysics.FixturesMass; - } - - private TimeSpan GetPickupDuration(EntityUid carrier, EntityUid carried) - { - var length = TimeSpan.FromSeconds(3); - - var mod = MassContest(carrier, carried); - if (mod != 0) - length /= mod; - - return length; - } - public override void Update(float frameTime) { var query = EntityQueryEnumerator(); @@ -429,4 +367,4 @@ public override void Update(float frameTime) query.Dispose(); } } -} \ No newline at end of file +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index d817ae3d7ae..728ee63b070 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -2279,5 +2279,62 @@ public static readonly CVarDef /// public static readonly CVarDef DebugPow3rDisableParallel = CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY); + + // Start EE Code + #region Contests System + + /// + /// The MASTER TOGGLE for the entire Contests System. + /// ALL CONTESTS BELOW, regardless of type or setting will output 1f when false. + /// + public static readonly CVarDef DoContestsSystem = + CVarDef.Create("contests.do_contests_system", true, CVar.REPLICATED | CVar.SERVER); + + /// + /// Contest functions normally include an optional override to bypass the clamp set by max_percentage. + /// This CVar disables the bypass when false, forcing all implementations to comply with max_percentage. + /// + public static readonly CVarDef AllowClampOverride = + CVarDef.Create("contests.allow_clamp_override", true, CVar.REPLICATED | CVar.SERVER); + /// + /// Toggles all MassContest functions. All mass contests output 1f when false + /// + public static readonly CVarDef DoMassContests = + CVarDef.Create("contests.do_mass_contests", true, CVar.REPLICATED | CVar.SERVER); + + /// + /// Toggles all StaminaContest functions. All stamina contests output 1f when false + /// + public static readonly CVarDef DoStaminaContests = + CVarDef.Create("contests.do_stamina_contests", true, CVar.REPLICATED | CVar.SERVER); + + /// + /// Toggles all HealthContest functions. All health contests output 1f when false + /// + public static readonly CVarDef DoHealthContests = + CVarDef.Create("contests.do_health_contests", true, CVar.REPLICATED | CVar.SERVER); + + /// + /// Toggles all MindContest functions. All mind contests output 1f when false. + /// MindContests are not currently implemented, and are awaiting completion of the Psionic Refactor + /// + public static readonly CVarDef DoMindContests = + CVarDef.Create("contests.do_mind_contests", true, CVar.REPLICATED | CVar.SERVER); + + /// + /// Toggles all MoodContest functions. All mood contests output 1f when false. + /// + public static readonly CVarDef DoMoodContests = + CVarDef.Create("contests.do_mood_contests", true, CVar.REPLICATED | CVar.SERVER); + + /// + /// The maximum amount that Mass Contests can modify a physics multiplier, given as a +/- percentage + /// Default of 0.25f outputs between * 0.75f and 1.25f + /// + public static readonly CVarDef MassContestsMaxPercentage = + CVarDef.Create("contests.max_percentage", 0.25f, CVar.REPLICATED | CVar.SERVER); + + #endregion + // End EE Code } } diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryDoAfterEvent.cs b/Content.Shared/Nyanotrasen/Carrying/CarryDoAfterEvent.cs deleted file mode 100644 index 940135bfa99..00000000000 --- a/Content.Shared/Nyanotrasen/Carrying/CarryDoAfterEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Serialization; - -namespace Content.Shared.Carrying; - -[Serializable, NetSerializable] -public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent -{ -} diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs deleted file mode 100644 index abf12ab4de7..00000000000 --- a/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.Serialization; -using Content.Shared.DoAfter; - -namespace Content.Shared.Carrying -{ -} - -[Serializable, NetSerializable] -public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent -{ -} - diff --git a/Content.Shared/_EE/Carrying/CarryingDoAfterEvent.cs b/Content.Shared/_EE/Carrying/CarryingDoAfterEvent.cs new file mode 100644 index 00000000000..fb7225461cb --- /dev/null +++ b/Content.Shared/_EE/Carrying/CarryingDoAfterEvent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Carrying +{ + [Serializable, NetSerializable] + public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent { } +} diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs b/Content.Shared/_EE/Carrying/CarryingSlowdownComponent.cs similarity index 72% rename from Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs rename to Content.Shared/_EE/Carrying/CarryingSlowdownComponent.cs index a4b0cc1781f..597edc2a795 100644 --- a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs +++ b/Content.Shared/_EE/Carrying/CarryingSlowdownComponent.cs @@ -7,15 +7,15 @@ namespace Content.Shared.Carrying public sealed partial class CarryingSlowdownComponent : Component { - [DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public float WalkModifier = 1.0f; - [DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public float SprintModifier = 1.0f; } [Serializable, NetSerializable] - public sealed partial class CarryingSlowdownComponentState : ComponentState + public sealed class CarryingSlowdownComponentState : ComponentState { public float WalkModifier; public float SprintModifier; diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs b/Content.Shared/_EE/Carrying/CarryingSlowdownSystem.cs similarity index 85% rename from Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs rename to Content.Shared/_EE/Carrying/CarryingSlowdownSystem.cs index 9b9c8cec10f..04b714fdd78 100644 --- a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs +++ b/Content.Shared/_EE/Carrying/CarryingSlowdownSystem.cs @@ -31,13 +31,12 @@ private void OnGetState(EntityUid uid, CarryingSlowdownComponent component, ref private void OnHandleState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentHandleState args) { - if (args.Current is CarryingSlowdownComponentState state) - { - component.WalkModifier = state.WalkModifier; - component.SprintModifier = state.SprintModifier; + if (args.Current is not CarryingSlowdownComponentState state) + return; - _movementSpeed.RefreshMovementSpeedModifiers(uid); - } + component.WalkModifier = state.WalkModifier; + component.SprintModifier = state.SprintModifier; + _movementSpeed.RefreshMovementSpeedModifiers(uid); } private void OnRefreshMoveSpeed(EntityUid uid, CarryingSlowdownComponent component, RefreshMovementSpeedModifiersEvent args) { diff --git a/Content.Shared/_EE/Contests/ContestsSystem.Utilities.cs b/Content.Shared/_EE/Contests/ContestsSystem.Utilities.cs new file mode 100644 index 00000000000..b0058d1ce29 --- /dev/null +++ b/Content.Shared/_EE/Contests/ContestsSystem.Utilities.cs @@ -0,0 +1,283 @@ +using Content.Shared.CCVar; +using Robust.Shared.Serialization; + +namespace Content.Shared.Contests; +public sealed partial class ContestsSystem +{ + /// + /// Clamp a contest to a Range of [Epsilon, 32bit integer limit]. This exists to make sure contests are always "Safe" to divide by. + /// + private float ContestClamp(float input) + { + return Math.Clamp(input, float.Epsilon, float.MaxValue); + } + + /// + /// Shorthand for checking if clamp overrides are allowed, and the bypass is used by a contest. + /// + private bool ContestClampOverride(bool bypassClamp) + { + return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp; + } + + /// + /// Constructor for feeding options from a given set of ContestArgs into the ContestsSystem. + /// Just multiply by this and give it a user EntityUid and a ContestArgs variable. That's all you need to know. + /// + public float ContestConstructor(EntityUid user, ContestArgs args) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1; + + if (!args.DoEveryInteraction) + return args.DoMassInteraction ? ((!args.MassDisadvantage + ? MassContest(user, args.MassBypassClamp, args.MassRangeModifier) + : 1 / MassContest(user, args.MassBypassClamp, args.MassRangeModifier)) + + args.MassOffset) + : 1 + * (args.DoStaminaInteraction ? ((!args.StaminaDisadvantage + ? StaminaContest(user, args.StaminaBypassClamp, args.StaminaRangeModifier) + : 1 / StaminaContest(user, args.StaminaBypassClamp, args.StaminaRangeModifier)) + + args.StaminaOffset) + : 1) + * (args.DoHealthInteraction ? ((!args.HealthDisadvantage + ? HealthContest(user, args.HealthBypassClamp, args.HealthRangeModifier) + : 1 / HealthContest(user, args.HealthBypassClamp, args.HealthRangeModifier)) + + args.HealthOffset) + : 1); + //* (args.DoMindInteraction ? ((!args.MindDisadvantage + // ? MindContest(user, args.MindBypassClamp, args.MindRangeModifier) + // : 1 / MindContest(user, args.MindBypassClamp, args.MindRangeModifier)) + // + args.MindOffset) + // : 1) + //* (args.DoMoodInteraction ? ((!args.MoodDisadvantage + // ? MoodContest(user, args.MoodBypassClamp, args.MoodRangeModifier) + // : 1 / MoodContest(user, args.MoodBypassClamp, args.MoodRangeModifier)) + // + args.MoodOffset) + // : 1); + + var everyContest = EveryContest(user, + args.MassBypassClamp, + args.StaminaBypassClamp, + args.HealthBypassClamp, + args.MindBypassClamp, + args.MoodBypassClamp, + args.MassRangeModifier, + args.StaminaRangeModifier, + args.HealthRangeModifier, + args.MindRangeModifier, + args.MoodRangeModifier, + args.EveryMassWeight, + args.EveryStaminaWeight, + args.EveryHealthWeight, + args.EveryMindWeight, + args.EveryMoodWeight, + args.EveryInteractionSumOrMultiply); + + return !args.EveryDisadvantage ? everyContest : 1 / everyContest; + } +} + +[Serializable, NetSerializable, DataDefinition] +public sealed partial class ContestArgs +{ + /// + /// Controls whether this melee weapon allows for mass to factor into damage. + /// + [DataField] + public bool DoMassInteraction; + + /// + /// When true, mass provides a disadvantage. + /// + [DataField] + public bool MassDisadvantage; + + /// + /// When true, mass contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool MassBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mass contests for melee. + /// + [DataField] + public float MassRangeModifier = 1; + + /// + /// The output of a mass contest is increased by this amount. + /// + [DataField] + public float MassOffset; + + /// + /// Controls whether this melee weapon allows for stamina to factor into damage. + /// + [DataField] + public bool DoStaminaInteraction; + + /// + /// When true, stamina provides a disadvantage. + /// + [DataField] + public bool StaminaDisadvantage; + + /// + /// When true, stamina contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool StaminaBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mass contests for melee. + /// + [DataField] + public float StaminaRangeModifier = 1; + + /// + /// The output of a stamina contest is increased by this amount. + /// + [DataField] + public float StaminaOffset; + + /// + /// Controls whether this melee weapon allows for health to factor into damage. + /// + [DataField] + public bool DoHealthInteraction; + + /// + /// When true, health contests provide a disadvantage. + /// + [DataField] + public bool HealthDisadvantage; + + /// + /// When true, health contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool HealthBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mass contests for melee. + /// + [DataField] + public float HealthRangeModifier = 1; + + /// + /// The output of health contests is increased by this amount. + /// + [DataField] + public float HealthOffset; + + /// + /// Controls whether this melee weapon allows for psychic casting stats to factor into damage. + /// + [DataField] + public bool DoMindInteraction; + + /// + /// When true, high psychic casting stats provide a disadvantage. + /// + [DataField] + public bool MindDisadvantage; + + /// + /// When true, mind contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool MindBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mind contests for melee. + /// + [DataField] + public float MindRangeModifier = 1; + + /// + /// The output of a mind contest is increased by this amount. + /// + [DataField] + public float MindOffset; + + /// + /// Controls whether this melee weapon allows mood to factor into damage. + /// + [DataField] + public bool DoMoodInteraction; + + /// + /// When true, mood provides a disadvantage. + /// + [DataField] + public bool MoodDisadvantage; + + /// + /// When true, mood contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool MoodBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mood contests for melee. + /// + [DataField] + public float MoodRangeModifier = 1; + + /// + /// The output of mood contests is increased by this amount. + /// + [DataField] + public float MoodOffset; + + /// + /// Enables the EveryContest interaction for a melee weapon. + /// IF YOU PUT THIS ON ANY WEAPON OTHER THAN AN ADMEME, I WILL COME TO YOUR HOUSE AND SEND YOU TO MEET YOUR CREATOR WHEN THE PLAYERS COMPLAIN. + /// + [DataField] + public bool DoEveryInteraction; + + /// + /// When true, EveryContest provides a disadvantage. + /// + [DataField] + public bool EveryDisadvantage; + + /// + /// How much Mass is considered for an EveryContest. + /// + [DataField] + public float EveryMassWeight = 1; + + /// + /// How much Stamina is considered for an EveryContest. + /// + [DataField] + public float EveryStaminaWeight = 1; + + /// + /// How much Health is considered for an EveryContest. + /// + [DataField] + public float EveryHealthWeight = 1; + + /// + /// How much psychic casting stats are considered for an EveryContest. + /// + [DataField] + public float EveryMindWeight = 1; + + /// + /// How much mood is considered for an EveryContest. + /// + [DataField] + public float EveryMoodWeight = 1; + + /// + /// When true, the EveryContest sums the results of all contests rather than multiplying them, + /// probably giving you a very, very, very large multiplier... + /// + [DataField] + public bool EveryInteractionSumOrMultiply; +} diff --git a/Content.Shared/_EE/Contests/ContestsSystem.cs b/Content.Shared/_EE/Contests/ContestsSystem.cs new file mode 100644 index 00000000000..8417c5087fc --- /dev/null +++ b/Content.Shared/_EE/Contests/ContestsSystem.cs @@ -0,0 +1,475 @@ +//using Content.Shared.Abilities.Psionics; +using Content.Shared.CCVar; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; +using Content.Shared.Mobs.Systems; +//using Content.Shared.Mood; +using Robust.Shared.Configuration; +using Robust.Shared.Physics.Components; + +namespace Content.Shared.Contests; + +public sealed partial class ContestsSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + + /// + /// The presumed average mass of a player entity + /// Defaulted to the average mass of an adult human + /// + private const float AverageMass = 71f; + + /// + /// The presumed average sum of a Psionic's Baseline Amplification and Baseline Dampening. + /// Since Baseline casting stats are a random value between 0.4 and 1.2, this is defaulted to 0.8 + 0.8. + /// + private const float AveragePsionicPotential = 1.6f; + + #region Mass Contests + /// + /// Outputs the ratio of mass between a performer and the average human mass + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(EntityUid performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || performerPhysics.Mass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass / otherMass + : Math.Clamp(performerPhysics.Mass / otherMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// + /// MaybeMassContest, in case your entity doesn't exist + /// + public float MassContest(EntityUid? performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerUid is null) + return 1f; + + return MassContest(performerUid.Value, bypassClamp, rangeFactor, otherMass); + } + + /// + /// Outputs the ratio of mass between a performer and the average human mass + /// If a function already has the performer's physics component, this is faster + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(PhysicsComponent performerPhysics, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerPhysics.Mass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass / otherMass + : Math.Clamp(performerPhysics.Mass / otherMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Outputs the ratio of mass between a performer and a target, accepts either EntityUids or PhysicsComponents in any combination + /// If you have physics components already in your function, use instead + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(EntityUid performerUid, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || !TryComp(targetUid, out var targetPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(EntityUid performerUid, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(PhysicsComponent performerPhysics, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(targetUid, out var targetPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(PhysicsComponent performerPhysics, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + #region Stamina Contests + + /// + /// Outputs 1 minus the percentage of an Entity's Stamina, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. + /// This will never return a value >1. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float StaminaContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!TryComp(performer, out var perfStamina) + || perfStamina.StaminaDamage == 0) + return 1f; + + return StaminaContest(perfStamina, bypassClamp, rangeFactor); + } + + /// + public float StaminaContest(StaminaComponent perfStamina, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoStaminaContests)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? 1 - perfStamina.StaminaDamage / perfStamina.CritThreshold + : 1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)); + } + + /// + /// Outputs the ratio of percentage of an Entity's Stamina and a Target Entity's Stamina, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. + /// This does NOT produce the same kind of outputs as a Single-Entity StaminaContest. 2Entity StaminaContest returns the product of two Solo Stamina Contests, and so its values can be very strange. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float StaminaContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoStaminaContests) + || !TryComp(performer, out var perfStamina) + || !TryComp(target, out var targetStamina)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? (1 - perfStamina.StaminaDamage / perfStamina.CritThreshold) + / (1 - targetStamina.StaminaDamage / targetStamina.CritThreshold) + : (1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)) + / (1 - Math.Clamp(targetStamina.StaminaDamage / targetStamina.CritThreshold, 0, 0.25f * rangeFactor))); + } + + #endregion + + #region Health Contests + + /// + /// Outputs 1 minus the percentage of an Entity's Health, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. + /// This will never return a value >1. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float HealthContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoHealthContests) + || !TryComp(performer, out var damage) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var threshold)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? 1 - damage.TotalDamage.Float() / threshold.Value.Float() + : 1 - Math.Clamp(damage.TotalDamage.Float() / threshold.Value.Float(), 0, 0.25f * rangeFactor)); + } + + /// + /// Outputs the ratio of percentage of an Entity's Health and a Target Entity's Health, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. + /// This does NOT produce the same kind of outputs as a Single-Entity HealthContest. 2Entity HealthContest returns the product of two Solo Health Contests, and so its values can be very strange. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float HealthContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoHealthContests) + || !TryComp(performer, out var perfDamage) + || !TryComp(target, out var targetDamage) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var perfThreshold) + || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, out var targetThreshold)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? (1 - perfDamage.TotalDamage.Float() / perfThreshold.Value.Float()) + / (1 - targetDamage.TotalDamage.Float() / targetThreshold.Value.Float()) + : (1 - Math.Clamp(perfDamage.TotalDamage.Float() / perfThreshold.Value.Float(), 0, 0.25f * rangeFactor)) + / (1 - Math.Clamp(targetDamage.TotalDamage.Float() / targetThreshold.Value.Float(), 0, 0.25f * rangeFactor))); + } + #endregion + + #region Mind Contests + + /// + /// Returns the ratio of casting stats between a performer and the presumed average latent psionic. + /// Uniquely among Contests, not being Psionic is not a failure condition, and is instead a variable. + /// If you do not have a PsionicComponent, your combined casting stats are assumed to be 0.1f + /// + /// + /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. + /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. + /// + //public float MindContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f, float otherPsion = AveragePsionicPotential) + //{ + // if (!_cfg.GetCVar(CCVars.DoContestsSystem) + // || !_cfg.GetCVar(CCVars.DoMindContests)) + // return 1f; + // + // var performerPotential = TryComp(performer, out var performerPsionic) + // ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening + // : 0.1f; + // + // if (performerPotential == otherPsion) + // return 1f; + // + // return ContestClamp(ContestClampOverride(bypassClamp) + // ? performerPotential / otherPsion + // : Math.Clamp(performerPotential / otherPsion, + // 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + // 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + //} + + /// + /// Returns the ratio of casting stats between a performer and a target. + /// Like with single-Uid MindContests, if an entity does not possess a PsionicComponent, its casting stats are assumed to be 0.1f. + /// + /// + /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. + /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. + /// + //public float MindContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + //{ + // if (!_cfg.GetCVar(CCVars.DoContestsSystem) + // || !_cfg.GetCVar(CCVars.DoMindContests)) + // return 1f; + // + // var performerPotential = TryComp(performer, out var performerPsionic) + // ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening + // : 0.1f; + // + // var targetPotential = TryComp(target, out var targetPsionic) + // ? targetPsionic.CurrentAmplification + targetPsionic.CurrentDampening + // : 0.1f; + // + // if (performerPotential == targetPotential) + // return 1f; + // + // return ContestClamp(ContestClampOverride(bypassClamp) + // ? performerPotential / targetPotential + // : Math.Clamp(performerPotential / targetPotential, + // 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + // 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + //} + + #endregion + + #region Mood Contests + + /// + /// Outputs the ratio of an Entity's mood level and its Neutral Mood threshold. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + //public float MoodContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + //{ + // if (!_cfg.GetCVar(CCVars.DoContestsSystem) + // || !_cfg.GetCVar(CCVars.DoMoodContests) + // || !TryComp(performer, out var mood)) + // return 1f; + // + // return ContestClamp(ContestClampOverride(bypassClamp) + // ? mood.CurrentMoodLevel / mood.NeutralMoodThreshold + // : Math.Clamp(mood.CurrentMoodLevel / mood.NeutralMoodThreshold, + // 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + // 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + //} + + /// + /// Outputs the ratio of mood level between two Entities. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + //public float MoodContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + //{ + // if (!_cfg.GetCVar(CCVars.DoContestsSystem) + // || !_cfg.GetCVar(CCVars.DoMoodContests) + // || !TryComp(performer, out var performerMood) + // || !TryComp(target, out var targetMood)) + // return 1f; + // + // return ContestClamp(ContestClampOverride(bypassClamp) + // ? performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel + // : Math.Clamp(performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel, + // 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + // 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + //} + + #endregion + + #region EVERY CONTESTS + + /// + /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. + /// + /// + /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. + /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. + /// + public float EveryContest( + EntityUid performer, + bool bypassClampMass = false, + bool bypassClampStamina = false, + bool bypassClampHealth = false, + bool bypassClampMind = false, + bool bypassClampMood = false, + float rangeFactorMass = 1f, + float rangeFactorStamina = 1f, + float rangeFactorHealth = 1f, + float rangeFactorMind = 1f, + float rangeFactorMood = 1f, + float weightMass = 1f, + float weightStamina = 1f, + float weightHealth = 1f, + float weightMind = 1f, + float weightMood = 1f, + bool sumOrMultiply = false) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1f; + + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; + var massMultiplier = weightMass / weightTotal; + var staminaMultiplier = weightStamina / weightTotal; + var healthMultiplier = weightHealth / weightTotal; + var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; + + return sumOrMultiply + ? MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + + StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + + HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + //+ MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + //+ MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier + : ContestClamp(MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + * StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + * HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + //* MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + //* MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier + ); + } + + /// + /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. + /// + /// + /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. + /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. + /// + public float EveryContest( + EntityUid performer, + EntityUid target, + bool bypassClampMass = false, + bool bypassClampStamina = false, + bool bypassClampHealth = false, + bool bypassClampMind = false, + bool bypassClampMood = false, + float rangeFactorMass = 1f, + float rangeFactorStamina = 1f, + float rangeFactorHealth = 1f, + float rangeFactorMind = 1f, + float rangeFactorMood = 1f, + float weightMass = 1f, + float weightStamina = 1f, + float weightHealth = 1f, + float weightMind = 1f, + float weightMood = 1f, + bool sumOrMultiply = false) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1f; + + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; + var massMultiplier = weightMass / weightTotal; + var staminaMultiplier = weightStamina / weightTotal; + var healthMultiplier = weightHealth / weightTotal; + var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; + + return sumOrMultiply + ? MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + + StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + + HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + //+ MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + //+ MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier + : ContestClamp(MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + * StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + * HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + //* MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + //* MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier + ); + } + #endregion +}