diff --git a/Content.Shared/Damage/Components/DamageOnInteractComponent.cs b/Content.Shared/Damage/Components/DamageOnInteractComponent.cs index 9487dec8ef..d5465f19bd 100644 --- a/Content.Shared/Damage/Components/DamageOnInteractComponent.cs +++ b/Content.Shared/Damage/Components/DamageOnInteractComponent.cs @@ -44,4 +44,46 @@ public sealed partial class DamageOnInteractComponent : Component /// [DataField, AutoNetworkedField] public bool IsDamageActive = true; + + /// + /// Whether the thing should be thrown from its current position when they interact with the entity + /// + [DataField] + public bool Throw = false; + + /// + /// The speed applied to the thing when it is thrown + /// + [DataField] + public int ThrowSpeed = 10; + + /// + /// Time between being able to interact with this entity + /// + [DataField] + public uint InteractTimer = 0; + + /// + /// Tracks the last time this entity was interacted with, but only if the interaction resulted in the user taking damage + /// + [DataField] + public TimeSpan LastInteraction = TimeSpan.Zero; + + /// + /// Tracks the time that this entity can be interacted with, but only if the interaction resulted in the user taking damage + /// + [DataField] + public TimeSpan NextInteraction = TimeSpan.Zero; + + /// + /// Probability that the user will be stunned when they interact with with this entity and took damage + /// + [DataField] + public float StunChance = 0.0f; + + /// + /// Duration, in seconds, of the stun applied to the user when they interact with the entity and took damage + /// + [DataField] + public float StunSeconds = 0.0f; } diff --git a/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs b/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs index cc3b3f6d5d..0f43e93abf 100644 --- a/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs +++ b/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs @@ -4,9 +4,15 @@ using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Popups; +using Robust.Shared.Random; +using Content.Shared.Throwing; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; using Robust.Shared.Timing; +using Content.Shared.Random; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Effects; +using Content.Shared.Stunnable; namespace Content.Shared.Damage.Systems; @@ -17,6 +23,10 @@ public sealed class DamageOnInteractSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly ThrowingSystem _throwingSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; public override void Initialize() { @@ -35,6 +45,13 @@ public sealed class DamageOnInteractSystem : EntitySystem /// Contains the user that interacted with the entity private void OnHandInteract(Entity entity, ref InteractHandEvent args) { + // Stop the interaction if the user attempts to interact with the object before the timer is finished + if (_gameTiming.CurTime < entity.Comp.NextInteraction) + { + args.Handled = true; + return; + } + if (!entity.Comp.IsDamageActive) return; @@ -47,9 +64,8 @@ public sealed class DamageOnInteractSystem : EntitySystem // or checking the entity for the comp itself if the inventory didn't work if (protectiveEntity.Comp == null && TryComp(args.User, out var protectiveComp)) - { protectiveEntity = (args.User, protectiveComp); - } + // if protectiveComp isn't null after all that, it means the user has protection, // so let's calculate how much they resist @@ -59,17 +75,31 @@ public sealed class DamageOnInteractSystem : EntitySystem } } - totalDamage = _damageableSystem.TryChangeDamage(args.User, totalDamage, origin: args.Target); + totalDamage = _damageableSystem.TryChangeDamage(args.User, totalDamage, origin: args.Target); if (totalDamage != null && totalDamage.AnyPositive()) { + // Record this interaction and determine when a user is allowed to interact with this entity again + entity.Comp.LastInteraction = _gameTiming.CurTime; + entity.Comp.NextInteraction = _gameTiming.CurTime + TimeSpan.FromSeconds(entity.Comp.InteractTimer); + args.Handled = true; _adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} injured their hand by interacting with {ToPrettyString(args.Target):target} and received {totalDamage.GetTotal():damage} damage"); _audioSystem.PlayPredicted(entity.Comp.InteractSound, args.Target, args.User); if (entity.Comp.PopupText != null) _popupSystem.PopupClient(Loc.GetString(entity.Comp.PopupText), args.User, args.User); + + // Attempt to paralyze the user after they have taken damage + if (_random.Prob(entity.Comp.StunChance)) + _stun.TryParalyze(args.User, TimeSpan.FromSeconds(entity.Comp.StunSeconds), true); } + // Check if the entity's Throw bool is false, or if the entity has the PullableComponent, then if the entity is currently being pulled. + // BeingPulled must be checked because the entity will be spastically thrown around without this. + if (!entity.Comp.Throw || !TryComp(entity, out var pullComp) || pullComp.BeingPulled) + return; + + _throwingSystem.TryThrow(entity, _random.NextVector2(), entity.Comp.ThrowSpeed, doSpin: true); } public void SetIsDamageActiveTo(Entity entity, bool mode) diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 20c2488aa7..6f23003c1a 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -171,6 +171,11 @@ fiberMaterial: fibers-leather fiberColor: fibers-brown - type: FingerprintMask + - type: DamageOnInteractProtection + damageProtection: + flatReductions: + Heat: 10 + Caustic: 5 - type: entity parent: ClothingHandsBase diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index ac9757edfb..0c79c233a4 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -250,7 +250,7 @@ name: death nettle description: This nettle's out for blood. id: DeathNettle - parent: ProduceBase + parent: [ProduceBase, BaseMajorContraband] components: - type: Sprite sprite: Objects/Specific/Hydroponics/death_nettle.rsi @@ -261,24 +261,56 @@ - type: MeleeWeapon damage: types: - Heat: 8.5 - Caustic: 8.5 + Heat: 8 + Caustic: 8 - type: SolutionContainerManager solutions: food: reagents: - ReagentId: SulfuricAcid - Quantity: 3 + Quantity: 15 - ReagentId: FluorosulfuricAcid - Quantity: 3 + Quantity: 15 - type: Produce seedId: deathNettle - type: MeleeChemicalInjector - transferAmount: 2 + transferAmount: 5 solution: food - pierceArmor: false + pierceArmor: true - type: Extractable grindableSolutionName: food + - type: DamageOnInteract + damage: + types: + Heat: 4 + Caustic: 4 + throw: true + throwSpeed: 3 + interactTimer: 2 # Stop the player from spam clicking the entity + ignoreResistances: false + popupText: powered-light-component-burn-hand + interactSound: /Audio/Effects/lightburn.ogg + stunChance: 0.10 + stunSeconds: 1.5 + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 25 + behaviors: + - !type:PlaySoundBehavior + sound: + path: /Audio/Voice/Diona/diona_salute.ogg + params: + volume: -5 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: DamageOnHit + damage: + types: + Blunt: 5 # The nettle will "wilt" after 5 hits. - type: entity name: banana @@ -1791,10 +1823,10 @@ - type: SolutionContainerManager solutions: food: - maxVol: 30 + maxVol: 15 reagents: - ReagentId: Amatoxin - Quantity: 25 + Quantity: 10 - ReagentId: Nutriment Quantity: 5 - type: Sprite diff --git a/Resources/Prototypes/Hydroponics/seeds.yml b/Resources/Prototypes/Hydroponics/seeds.yml index ed5f61922a..68524b15ff 100644 --- a/Resources/Prototypes/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Hydroponics/seeds.yml @@ -1402,8 +1402,8 @@ chemicals: Amatoxin: Min: 1 - Max: 25 - PotencyDivisor: 4 + Max: 10 + PotencyDivisor: 12 Nutriment: ## yumby :) Min: 1 Max: 5