diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 96669d2bb5..14086f402f 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -32,6 +32,7 @@ namespace Content.Client.Entry "PoweredLight", "Smes", "LightBulb", + "GlassTable", "Healing", "Material", "RandomAppearance", diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index 9921bdb333..dce147c66e 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -1,14 +1,19 @@ +using System; using System.Collections.Generic; using System.Linq; using Content.Server.Climbing.Components; +using Content.Server.Popups; +using Content.Server.Stunnable; using Content.Shared.ActionBlocker; using Content.Shared.Climbing; +using Content.Shared.Damage; using Content.Shared.GameTicking; using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Player; namespace Content.Server.Climbing { @@ -18,6 +23,9 @@ namespace Content.Server.Climbing private readonly HashSet _activeClimbers = new(); [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly StunSystem _stunSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; public override void Initialize() { @@ -25,6 +33,7 @@ namespace Content.Server.Climbing SubscribeLocalEvent(Reset); SubscribeLocalEvent(AddClimbVerb); + SubscribeLocalEvent(OnGlassClimbed); } public void ForciblySetClimbing(EntityUid uid, ClimbingComponent? component = null) @@ -47,12 +56,24 @@ namespace Content.Server.Climbing // Add a climb verb Verb verb = new(); - verb.Act = () => component.TryClimb(args.User); + verb.Act = () => component.TryClimb(args.User, args.Target); verb.Text = Loc.GetString("comp-climbable-verb-climb"); // TODO VERBS ICON add a climbing icon? args.Verbs.Add(verb); } + private void OnGlassClimbed(EntityUid uid, GlassTableComponent component, ClimbedOnEvent args) + { + _damageableSystem.TryChangeDamage(args.Climber, component.ClimberDamage); + _damageableSystem.TryChangeDamage(uid, component.TableDamage); + _stunSystem.TryParalyze(args.Climber, TimeSpan.FromSeconds(component.StunTime), true); + + // Not shown to the user, since they already get a 'you climb on the glass table' popup + _popupSystem.PopupEntity(Loc.GetString("glass-table-shattered-others", + ("table", uid), ("climber", args.Climber)), args.Climber, + Filter.Pvs(uid).RemoveWhereAttachedEntity(puid => puid == args.Climber)); + } + public void AddActiveClimber(ClimbingComponent climbingComponent) { _activeClimbers.Add(climbingComponent); diff --git a/Content.Server/Climbing/Components/ClimbableComponent.cs b/Content.Server/Climbing/Components/ClimbableComponent.cs index 6188b24f29..df8a23d782 100644 --- a/Content.Server/Climbing/Components/ClimbableComponent.cs +++ b/Content.Server/Climbing/Components/ClimbableComponent.cs @@ -139,17 +139,17 @@ namespace Content.Server.Climbing.Components { if (eventArgs.User == eventArgs.Dragged) { - TryClimb(eventArgs.User); + TryClimb(eventArgs.User, eventArgs.Target); } else { - TryMoveEntity(eventArgs.User, eventArgs.Dragged); + TryMoveEntity(eventArgs.User, eventArgs.Dragged, eventArgs.Target); } return true; } - private async void TryMoveEntity(EntityUid user, EntityUid entityToMove) + private async void TryMoveEntity(EntityUid user, EntityUid entityToMove, EntityUid climbable) { var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, entityToMove) { @@ -184,6 +184,9 @@ namespace Content.Server.Climbing.Components // we may potentially need additional logic since we're forcing a player onto a climbable // there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for + _entities.EventBus.RaiseLocalEvent(entityToMove, new StartClimbEvent(climbable), false); + _entities.EventBus.RaiseLocalEvent(climbable, new ClimbedOnEvent(entityToMove), false); + var othersMessage = Loc.GetString("comp-climbable-user-climbs-force-other", ("user", user), ("moved-user", entityToMove), ("climbable", Owner)); user.PopupMessageOtherClients(othersMessage); @@ -193,7 +196,7 @@ namespace Content.Server.Climbing.Components } } - public async void TryClimb(EntityUid user) + public async void TryClimb(EntityUid user, EntityUid climbable) { if (!_entities.TryGetComponent(user, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing) return; @@ -216,6 +219,9 @@ namespace Content.Server.Climbing.Components var direction = (_entities.GetComponent(Owner).WorldPosition - userPos).Normalized; var endPoint = _entities.GetComponent(Owner).WorldPosition; + _entities.EventBus.RaiseLocalEvent(user, new StartClimbEvent(climbable), false); + _entities.EventBus.RaiseLocalEvent(climbable, new ClimbedOnEvent(user), false); + var climbMode = _entities.GetComponent(user); climbMode.IsClimbing = true; @@ -239,3 +245,29 @@ namespace Content.Server.Climbing.Components } } } + +/// +/// Raised on an entity when it is climbed on. +/// +public class ClimbedOnEvent : EntityEventArgs +{ + public EntityUid Climber; + + public ClimbedOnEvent(EntityUid climber) + { + Climber = climber; + } +} + +/// +/// Raised on an entity when it successfully climbs on something. +/// +public class StartClimbEvent : EntityEventArgs +{ + public EntityUid Climbable; + + public StartClimbEvent(EntityUid climbable) + { + Climbable = climbable; + } +} diff --git a/Content.Server/Climbing/Components/GlassTableComponent.cs b/Content.Server/Climbing/Components/GlassTableComponent.cs new file mode 100644 index 0000000000..9283bed861 --- /dev/null +++ b/Content.Server/Climbing/Components/GlassTableComponent.cs @@ -0,0 +1,33 @@ +using Content.Shared.Damage; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Climbing.Components; + +/// +/// Glass tables shatter and stun you when climbed on. +/// This is a really entity-specific behavior, so opted to make it +/// not very generalized with regards to naming. +/// +[RegisterComponent, Friend(typeof(ClimbSystem))] +[ComponentProtoName("GlassTable")] +public class GlassTableComponent : Component +{ + /// + /// How much damage should be given to the climber? + /// + [DataField("climberDamage")] + public DamageSpecifier ClimberDamage = default!; + + /// + /// How much damage should be given to the table when climbed on? + /// + [DataField("tableDamage")] + public DamageSpecifier TableDamage = default!; + + /// + /// How long should someone who climbs on this table be stunned for? + /// + public float StunTime = 5.0f; +} diff --git a/Resources/Locale/en-US/climbing/glass-table-component.ftl b/Resources/Locale/en-US/climbing/glass-table-component.ftl new file mode 100644 index 0000000000..1e7a159aed --- /dev/null +++ b/Resources/Locale/en-US/climbing/glass-table-component.ftl @@ -0,0 +1,4 @@ +### Tables which take damage when a user is dragged onto them + +## Showed to users other than the climber +glass-table-shattered-others = { CAPITALIZE(THE($table)) } cracks under the weight of { THE($climber) }! diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml index 52e6fc1598..4569814297 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml @@ -1,6 +1,7 @@ - type: entity id: TableFrame - parent: TableBase + # BaseStructure and not BaseTable, since these shouldn't be climbable/placeable. + parent: BaseStructure name: table frame description: Pieces of metal that make the frame of a table. components: @@ -8,9 +9,21 @@ sprite: Structures/Furniture/Tables/frame.rsi - type: Icon sprite: Structures/Furniture/Tables/frame.rsi + - type: Fixtures + fixtures: + - shape: + !type:PhysShapeAabb + bounds: "-0.45,-0.45,0.45,0.45" + mass: 50 + mask: + - Impassable + - VaultImpassable - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic + - type: IconSmooth + key: state + base: state_ - type: Destructible thresholds: - trigger: @@ -104,6 +117,13 @@ sprite: Structures/Furniture/Tables/glass.rsi - type: Icon sprite: Structures/Furniture/Tables/glass.rsi + - type: GlassTable + climberDamage: + types: + Slash: 15 + tableDamage: + types: + Blunt: 25 - type: Destructible thresholds: - trigger: @@ -118,6 +138,8 @@ ShardGlass: min: 1 max: 1 + - !type:ChangeConstructionNodeBehavior + node: TableFrame - !type:DoActsBehavior acts: [ "Destruction" ] - type: Construction @@ -134,6 +156,13 @@ sprite: Structures/Furniture/Tables/r_glass.rsi - type: Icon sprite: Structures/Furniture/Tables/r_glass.rsi + - type: GlassTable + climberDamage: + types: + Slash: 25 + tableDamage: + types: + Blunt: 40 - type: Destructible thresholds: - trigger: @@ -143,6 +172,8 @@ - !type:PlaySoundBehavior sound: path: /Audio/Effects/glass_break2.ogg + - !type:ChangeConstructionNodeBehavior + node: TableFrame - !type:SpawnEntitiesBehavior spawn: ShardGlass: @@ -167,15 +198,24 @@ sprite: Structures/Furniture/Tables/plasma.rsi - type: Icon sprite: Structures/Furniture/Tables/plasma.rsi + - type: GlassTable + climberDamage: + types: + Slash: 30 + tableDamage: + types: + Blunt: 100 - type: Destructible thresholds: - trigger: !type:DamageTrigger - damage: 20 + damage: 50 behaviors: - !type:PlaySoundBehavior sound: path: /Audio/Effects/glass_break2.ogg + - !type:ChangeConstructionNodeBehavior + node: TableFrame - !type:SpawnEntitiesBehavior spawn: ShardGlassPlasma: