diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index f114e225ec..4c23856a4c 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -359,6 +359,8 @@ namespace Content.Client.Entry "Thirst", "CanEscapeInventory", "PowerSink", + "Mousetrap", + "DamageOnTrigger", "Wires", }; } diff --git a/Content.Server/Damage/Components/DamageOnTriggerComponent.cs b/Content.Server/Damage/Components/DamageOnTriggerComponent.cs new file mode 100644 index 0000000000..b73c0164ab --- /dev/null +++ b/Content.Server/Damage/Components/DamageOnTriggerComponent.cs @@ -0,0 +1,12 @@ +using Content.Shared.Damage; + +namespace Content.Server.Damage.Components; + +[RegisterComponent] +public sealed class DamageOnTriggerComponent : Component +{ + [DataField("ignoreResistances")] public bool IgnoreResistances; + + [DataField("damage", required: true)] + public DamageSpecifier Damage = default!; +} diff --git a/Content.Server/Damage/Systems/DamageOnTriggerSystem.cs b/Content.Server/Damage/Systems/DamageOnTriggerSystem.cs new file mode 100644 index 0000000000..e17baf6432 --- /dev/null +++ b/Content.Server/Damage/Systems/DamageOnTriggerSystem.cs @@ -0,0 +1,49 @@ +using Content.Server.Damage.Components; +using Content.Shared.Damage; +using Content.Shared.StepTrigger; + +namespace Content.Server.Damage.Systems; + +// System for damage that occurs on specific triggers. +// This is originally meant for mousetraps, but could +// probably be extended to fit other triggers as well. +public sealed class DamageOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnStepTrigger); + } + + private void OnStepTrigger(EntityUid uid, DamageOnTriggerComponent component, ref StepTriggeredEvent args) + { + OnDamageTrigger(uid, args.Tripper, component); + } + + private void OnDamageTrigger(EntityUid source, EntityUid target, DamageOnTriggerComponent? component = null) + { + if (!Resolve(source, ref component)) + { + return; + } + + var damage = new DamageSpecifier(component.Damage); + var ev = new BeforeDamageOnTriggerEvent(damage, target); + RaiseLocalEvent(source, ev); + + _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances); + } +} + +public sealed class BeforeDamageOnTriggerEvent : EntityEventArgs +{ + public DamageSpecifier Damage { get; set; } + public EntityUid Tripper { get; } + + public BeforeDamageOnTriggerEvent(DamageSpecifier damage, EntityUid target) + { + Damage = damage; + Tripper = target; + } +} diff --git a/Content.Server/Mousetrap/MousetrapComponent.cs b/Content.Server/Mousetrap/MousetrapComponent.cs new file mode 100644 index 0000000000..c06986782f --- /dev/null +++ b/Content.Server/Mousetrap/MousetrapComponent.cs @@ -0,0 +1,21 @@ +namespace Content.Server.Mousetrap; + +[RegisterComponent] +public sealed class MousetrapComponent : Component +{ + [ViewVariables] + public bool IsActive; + + /// + /// Set this to change where the + /// inflection point in the scaling + /// equation will occur. + /// The default is 10. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("massBalance")] + public int MassBalance = 10; + + [DataField("ignoreDamageIfInventorySlotsFilled")] + public List IgnoreDamageIfSlotFilled = new(); +} diff --git a/Content.Server/Mousetrap/MousetrapSystem.cs b/Content.Server/Mousetrap/MousetrapSystem.cs new file mode 100644 index 0000000000..449e5f1d2b --- /dev/null +++ b/Content.Server/Mousetrap/MousetrapSystem.cs @@ -0,0 +1,82 @@ +using Content.Server.Damage.Systems; +using Content.Server.Explosion.EntitySystems; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; +using Content.Shared.Mousetrap; +using Content.Shared.StepTrigger; + +namespace Content.Server.Mousetrap; + +public sealed class MousetrapSystem : EntitySystem +{ + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly TriggerSystem _triggerSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(BeforeDamageOnTrigger); + SubscribeLocalEvent(OnStepTriggerAttempt); + SubscribeLocalEvent(OnStepTrigger); + } + + private void OnUseInHand(EntityUid uid, MousetrapComponent component, UseInHandEvent args) + { + component.IsActive = !component.IsActive; + + UpdateVisuals(uid); + } + + private void OnStepTriggerAttempt(EntityUid uid, MousetrapComponent component, ref StepTriggerAttemptEvent args) + { + args.Continue = component.IsActive; + } + + private void BeforeDamageOnTrigger(EntityUid uid, MousetrapComponent component, BeforeDamageOnTriggerEvent args) + { + foreach (var slot in component.IgnoreDamageIfSlotFilled) + { + if (!_inventorySystem.TryGetSlotContainer(args.Tripper, slot, out var container, out _)) + { + continue; + } + + // This also means that wearing slippers won't + // hurt the entity. + if (container.ContainedEntity != null) + { + args.Damage *= 0; + return; + } + } + + if (TryComp(args.Tripper, out PhysicsComponent? physics)) + { + // The idea here is inverse, + // Small - big damage, + // Large - small damage + // yes i punched numbers into a calculator until the graph looked right + var scaledDamage = -50 * Math.Atan(physics.Mass - component.MassBalance) + (25 * Math.PI); + args.Damage *= scaledDamage; + } + } + + private void OnStepTrigger(EntityUid uid, MousetrapComponent component, ref StepTriggeredEvent args) + { + component.IsActive = false; + _triggerSystem.Trigger(uid); + + UpdateVisuals(uid); + } + + private void UpdateVisuals(EntityUid uid, MousetrapComponent? mousetrap = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref mousetrap, ref appearance, false)) + { + return; + } + + appearance.SetData(MousetrapVisuals.Visual, + mousetrap.IsActive ? MousetrapVisuals.Armed : MousetrapVisuals.Unarmed); + } +} diff --git a/Content.Shared/Mousetrap/MousetrapVisuals.cs b/Content.Shared/Mousetrap/MousetrapVisuals.cs new file mode 100644 index 0000000000..9685157aad --- /dev/null +++ b/Content.Shared/Mousetrap/MousetrapVisuals.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Mousetrap; + +[Serializable, NetSerializable] +public enum MousetrapVisuals : byte +{ + Visual, + Armed, + Unarmed +} diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml index 90423e55e1..9235bd1692 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml @@ -22,6 +22,18 @@ category: Service group: market +- type: cargoProduct + name: "mousetraps crate" + id: MousetrapBoxes + description: "Mousetraps, for when all of service is being haunted by an entire horde of rats. Use sparingly... or not." + icon: + sprite: Objects/Devices/mousetrap.rsi + state: normal + product: CrateMousetrapBoxes + cost: 1000 + category: Service + group: market + - type: cargoProduct name: "lung cancer crate" id: ServiceSmokeables @@ -57,7 +69,7 @@ cost: 1000 category: Service group: market - + - type: cargoProduct name: "personnel crate" id: ServicePersonnel diff --git a/Resources/Prototypes/Catalog/Fills/Boxes/general.yml b/Resources/Prototypes/Catalog/Fills/Boxes/general.yml index bbf620d3c6..c11b9e2bfe 100644 --- a/Resources/Prototypes/Catalog/Fills/Boxes/general.yml +++ b/Resources/Prototypes/Catalog/Fills/Boxes/general.yml @@ -7,6 +7,21 @@ - type: Sprite state: box +- type: entity + name: mousetrap box + parent: BoxCardboard + id: BoxMousetrap + description: This box is filled with mousetraps. Try not to get your hand stuck in one. + components: + - type: StorageFill + contents: + - id: Mousetrap + amount: 6 + - type: Sprite + layers: + - state: box + - state: mousetraps + - type: entity name: lightbulb box parent: BoxCardboard diff --git a/Resources/Prototypes/Catalog/Fills/Crates/service.yml b/Resources/Prototypes/Catalog/Fills/Crates/service.yml index 2f63f62905..d5e64325e6 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/service.yml @@ -34,6 +34,17 @@ - id: BoxLightbulb amount: 1 +- type: entity + id: CrateMousetrapBoxes + name: mousetraps crate + description: Mousetraps, for when all of service is being haunted by an entire horde of rats. Use sparingly... or not. + parent: CrateGenericSteel + components: + - type: StorageFill + contents: + - id: BoxMousetrap + amount: 1 + - type: entity id: CrateServiceSmokeables name: smokeables crate diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 232de81a80..7ac057d455 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -30,6 +30,8 @@ contents: - id: CrowbarRed - id: MonkeyCubeBox + - id: BoxMousetrap + amount: 2 - id: SprayBottleWater - id: ReagentContainerFlour amount: 2 @@ -49,6 +51,8 @@ - type: StorageFill contents: - id: MopItem + - id: BoxMousetrap + amount: 2 - id: WetFloorSign amount: 3 - id: TrashBag diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 979563bcdd..9ba6773557 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -507,7 +507,7 @@ - shape: !type:PhysShapeCircle radius: 0.48 - mass: 20 + mass: 60 mask: - MobMask layer: @@ -560,7 +560,7 @@ crit: kangaroo-dead dead: kangaroo-dead - type: Puller - + - type: entity name: boxing kangaroo parent: MobKangaroo @@ -997,7 +997,7 @@ - shape: !type:PhysShapeCircle radius: 0.25 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -1213,7 +1213,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -1269,7 +1269,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -1325,7 +1325,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 5f72794ecb..688e01bad0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -19,7 +19,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -138,7 +138,7 @@ spawned: - id: FoodMeatCorgi amount: 2 - + - type: entity name: Old Ian parent: MobCorgi @@ -225,7 +225,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -375,7 +375,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -418,7 +418,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -472,7 +472,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -515,7 +515,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: @@ -592,7 +592,7 @@ - shape: !type:PhysShapeCircle radius: 0.35 - mass: 10 + mass: 20 mask: - MobMask layer: diff --git a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml new file mode 100644 index 0000000000..8ca251110d --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml @@ -0,0 +1,58 @@ +- type: entity + name: mousetrap + parent: BaseItem + id: Mousetrap + components: + - type: Sprite + sprite: Objects/Devices/mousetrap.rsi + netsync: false + drawdepth: SmallMobs # if mice can hide under tables, so can mousetraps + layers: + - state: mousetrap + - type: StepTrigger + intersectRatio: 0.2 + requiredTriggeredSpeed: 0 + - type: Mousetrap + ignoreDamageIfInventorySlotsFilled: + - shoes # shoes + - type: DamageOnTrigger + damage: + types: + Blunt: 2 # base damage, scales based on mass + - type: EmitSoundOnUse + sound: "/Audio/Items/Handcuffs/cuff_end.ogg" + - type: EmitSoundOnTrigger + sound: "/Audio/Items/snap.ogg" + - type: Item + sprite: Objects/Devices/mousetrap.rsi + - type: Appearance + visuals: + - type: GenericEnumVisualizer + key: enum.MousetrapVisuals.Visual + layer: 0 + states: + enum.MousetrapVisuals.Armed: mousetraparmed + enum.MousetrapVisuals.Unarmed: mousetrap + - type: Physics + bodyType: Dynamic + - type: CollisionWake + enabled: false + - type: Fixtures + fixtures: + - shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + id: "slips" + hard: false + layer: + - LowImpassable + - shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + mass: 5 + mask: + - ItemMask + - type: Rotatable + - type: Tag + tags: + - DroneUsable diff --git a/Resources/Textures/Objects/Devices/mousetrap.rsi/meta.json b/Resources/Textures/Objects/Devices/mousetrap.rsi/meta.json new file mode 100644 index 0000000000..cf36cb1862 --- /dev/null +++ b/Resources/Textures/Objects/Devices/mousetrap.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/4323540b6bec3ae93bbf13d685c2dbe0cb40a36e", + + "size": { + "x": 32, + "y": 32 + }, + + "states": [ + { + "name": "mousetrap", + "directions": 4 + }, + + { + "name": "mousetraparmed", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Devices/mousetrap.rsi/mousetrap.png b/Resources/Textures/Objects/Devices/mousetrap.rsi/mousetrap.png new file mode 100644 index 0000000000..d751807318 Binary files /dev/null and b/Resources/Textures/Objects/Devices/mousetrap.rsi/mousetrap.png differ diff --git a/Resources/Textures/Objects/Devices/mousetrap.rsi/mousetraparmed.png b/Resources/Textures/Objects/Devices/mousetrap.rsi/mousetraparmed.png new file mode 100644 index 0000000000..fc0257af24 Binary files /dev/null and b/Resources/Textures/Objects/Devices/mousetrap.rsi/mousetraparmed.png differ