From c00bd9c247ee929ff8fa9f9ab234487eb30bf225 Mon Sep 17 00:00:00 2001 From: Slava0135 <40753025+Slava0135@users.noreply.github.com> Date: Sat, 22 Apr 2023 14:40:36 +0300 Subject: [PATCH] Add hot potato (#14204) Co-authored-by: AJCM --- Content.Client/HotPotato/HotPotatoSystem.cs | 26 +++++++++ .../Explosion/EntitySystems/TriggerSystem.cs | 9 +++ Content.Server/HotPotato/HotPotatoSystem.cs | 55 ++++++++++++++++++ .../EntitySystems/SharedHandsSystem.Pickup.cs | 23 ++++++++ .../HotPotato/ActiveHotPotatoComponent.cs | 23 ++++++++ .../HotPotato/HotPotatoComponent.cs | 20 +++++++ .../HotPotato/SharedHotPotatoSystem.cs | 18 ++++++ .../Locale/en-US/hot-potato/hot-potato.ftl | 2 + .../Locale/en-US/store/uplink-catalog.ftl | 3 + .../Prototypes/Catalog/uplink_catalog.yml | 17 ++++++ .../Entities/Objects/Weapons/Bombs/funny.yml | 55 ++++++++++++++++++ .../Bombs/hot_potato.rsi/activated.png | Bin 0 -> 1318 bytes .../Weapons/Bombs/hot_potato.rsi/icon.png | Bin 0 -> 533 bytes .../Weapons/Bombs/hot_potato.rsi/meta.json | 23 ++++++++ 14 files changed, 274 insertions(+) create mode 100644 Content.Client/HotPotato/HotPotatoSystem.cs create mode 100644 Content.Server/HotPotato/HotPotatoSystem.cs create mode 100644 Content.Shared/HotPotato/ActiveHotPotatoComponent.cs create mode 100644 Content.Shared/HotPotato/HotPotatoComponent.cs create mode 100644 Content.Shared/HotPotato/SharedHotPotatoSystem.cs create mode 100644 Resources/Locale/en-US/hot-potato/hot-potato.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml create mode 100644 Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/activated.png create mode 100644 Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/icon.png create mode 100644 Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/meta.json diff --git a/Content.Client/HotPotato/HotPotatoSystem.cs b/Content.Client/HotPotato/HotPotatoSystem.cs new file mode 100644 index 0000000000..8e9c0f9e19 --- /dev/null +++ b/Content.Client/HotPotato/HotPotatoSystem.cs @@ -0,0 +1,26 @@ +using Content.Shared.HotPotato; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +public sealed class HotPotatoSystem : SharedHotPotatoSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + var query = AllEntityQuery(); + while (query.MoveNext(out var uid, out var comp)) + { + if (_timing.CurTime < comp.TargetTime) + continue; + comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown); + Spawn("HotPotatoEffect", Transform(uid).MapPosition.Offset(_random.NextVector2(0.25f))); + } + } +} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 10539dd860..6e4360b5f8 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -36,6 +36,12 @@ namespace Content.Server.Explosion.EntitySystems } } + /// + /// Raised when timer trigger becomes active. + /// + [ByRefEvent] + public readonly record struct ActiveTimerTriggerEvent(EntityUid Triggered, EntityUid? User); + [UsedImplicitly] public sealed partial class TriggerSystem : EntitySystem { @@ -184,6 +190,9 @@ namespace Content.Server.Explosion.EntitySystems active.BeepInterval = beepInterval; active.TimeUntilBeep = initialBeepDelay == null ? active.BeepInterval : initialBeepDelay.Value; + var ev = new ActiveTimerTriggerEvent(uid, user); + RaiseLocalEvent(uid, ref ev); + if (TryComp(uid, out var appearance)) _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance); } diff --git a/Content.Server/HotPotato/HotPotatoSystem.cs b/Content.Server/HotPotato/HotPotatoSystem.cs new file mode 100644 index 0000000000..79be700629 --- /dev/null +++ b/Content.Server/HotPotato/HotPotatoSystem.cs @@ -0,0 +1,55 @@ +using Content.Server.Explosion.EntitySystems; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.HotPotato; +using Content.Shared.Popups; +using Content.Shared.Weapons.Melee.Events; + +namespace Content.Server.HotPotato; + +public sealed class HotPotatoSystem : SharedHotPotatoSystem +{ + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnActiveTimer); + SubscribeLocalEvent(OnMeleeHit); + } + + private void OnActiveTimer(EntityUid uid, HotPotatoComponent comp, ref ActiveTimerTriggerEvent args) + { + EnsureComp(uid); + comp.CanTransfer = false; + Dirty(comp); + } + + private void OnMeleeHit(EntityUid uid, HotPotatoComponent comp, MeleeHitEvent args) + { + if (!HasComp(uid)) + return; + + comp.CanTransfer = true; + foreach (var hitEntity in args.HitEntities) + { + if (!TryComp(hitEntity, out var hands)) + continue; + + if (!_hands.IsHolding(hitEntity, uid, out _, hands) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands)) + { + _popup.PopupEntity(Loc.GetString("hot-potato-passed", + ("from", args.User), ("to", hitEntity)), uid, PopupType.Medium); + break; + } + + _popup.PopupEntity(Loc.GetString("hot-potato-failed", + ("to", hitEntity)), uid, PopupType.Medium); + + break; + } + comp.CanTransfer = false; + Dirty(comp); + } +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index 9732d3c215..846d67849c 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -104,6 +104,29 @@ public abstract partial class SharedHandsSystem : EntitySystem return true; } + /// + /// Tries to pick up an entity into any hand, forcing to drop an item if there are no free hands + /// By default it does check if it's possible to drop items + /// + public bool TryForcePickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (TryPickupAnyHand(uid, entity, checkActionBlocker: checkActionBlocker, handsComp: handsComp)) + return true; + + foreach (var hand in handsComp.Hands.Values) + { + if (TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp) && + TryPickup(uid, entity, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp)) + { + return true; + } + } + return false; + } + public bool CanPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null) { if (!Resolve(uid, ref handsComp, false)) diff --git a/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs b/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs new file mode 100644 index 0000000000..3a2a343783 --- /dev/null +++ b/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.HotPotato; + +/// +/// Added to an activated hot potato. Controls hot potato transfer on server / effect spawning on client. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedHotPotatoSystem))] +public sealed class ActiveHotPotatoComponent : Component +{ + /// + /// Hot potato effect spawn cooldown in seconds + /// + [DataField("effectCooldown"), ViewVariables(VVAccess.ReadWrite)] + public float EffectCooldown = 0.3f; + + /// + /// Moment in time next effect will be spawned + /// + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan TargetTime = TimeSpan.Zero; +} diff --git a/Content.Shared/HotPotato/HotPotatoComponent.cs b/Content.Shared/HotPotato/HotPotatoComponent.cs new file mode 100644 index 0000000000..2d02e10626 --- /dev/null +++ b/Content.Shared/HotPotato/HotPotatoComponent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.HotPotato; + +/// +/// Similar to +/// except entities with this component can be removed in specific case: +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] +[Access(typeof(SharedHotPotatoSystem))] +public sealed partial class HotPotatoComponent : Component +{ + /// + /// If set to true entity can be removed by hitting entities if they have hands + /// + [DataField("canTransfer"), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public bool CanTransfer = true; +} diff --git a/Content.Shared/HotPotato/SharedHotPotatoSystem.cs b/Content.Shared/HotPotato/SharedHotPotatoSystem.cs new file mode 100644 index 0000000000..cd7a5d6da5 --- /dev/null +++ b/Content.Shared/HotPotato/SharedHotPotatoSystem.cs @@ -0,0 +1,18 @@ +using Robust.Shared.Containers; + +namespace Content.Shared.HotPotato; + +public abstract class SharedHotPotatoSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRemoveAttempt); + } + + private void OnRemoveAttempt(EntityUid uid, HotPotatoComponent comp, ContainerGettingRemovedAttemptEvent args) + { + if (!comp.CanTransfer) + args.Cancel(); + } +} diff --git a/Resources/Locale/en-US/hot-potato/hot-potato.ftl b/Resources/Locale/en-US/hot-potato/hot-potato.ftl new file mode 100644 index 0000000000..76a691287c --- /dev/null +++ b/Resources/Locale/en-US/hot-potato/hot-potato.ftl @@ -0,0 +1,2 @@ +hot-potato-passed = {$from} passed hot potato to {$to}! +hot-potato-failed = Can't pass the potato to {$to}! diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 0c5225b619..0341c9a387 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -157,6 +157,9 @@ uplink-revolver-cap-gun-fake-desc = Fool your enemy! It can use both cap and mag uplink-banana-peel-explosive-name = Explosive Banana Peel uplink-banana-peel-explosive-desc = They will burst into laughter when they slip on it! +uplink-hot-potato-name = Hot Potato +uplink-hot-potato-desc = Once activated, this time bomb can't be dropped - only passed to someone else! + # Armor uplink-chameleon-name = Chameleon Kit uplink-chameleon-desc = A backpack full of items that contain chameleon technology allowing you to disguise as pretty much anything on the station, and more! diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index d42987c09e..b73174416c 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -620,6 +620,23 @@ whitelist: - Clown +- type: listing + id: uplinkHotPotato + name: uplink-hot-potato-name + description: uplink-hot-potato-desc + productEntity: HotPotato + cost: + Telecrystal: 4 + categories: + - UplinkJob + conditions: + - !type:BuyerJobCondition + whitelist: + - Chef + - Botanist + - Clown + - Mime + # Armor - type: listing diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml new file mode 100644 index 0000000000..c7c7f272fc --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml @@ -0,0 +1,55 @@ +- type: entity + name: hot potato + description: Once activated, this time bomb can't be dropped - only passed to someone else! + parent: BaseItem + id: HotPotato + components: + - type: Sprite + sprite: Objects/Weapons/Bombs/hot_potato.rsi + state: icon + netsync: false + - type: Item + sprite: Objects/Weapons/Bombs/hot_potato.rsi + size: 5 + - type: MeleeWeapon + damage: + types: + Blunt: 5 + - type: OnUseTimerTrigger + delay: 180 + beepSound: /Audio/Machines/Nuke/general_beep.ogg + - type: ExplodeOnTrigger + - type: Explosive + explosionType: Default + maxIntensity: 8 + intensitySlope: 5 + totalIntensity: 20 + canCreateVacuum: false + - type: DeleteOnTrigger + - type: HotPotato + - type: Appearance + visuals: + - type: GenericEnumVisualizer + key: enum.Trigger.TriggerVisuals.VisualState + states: + enum.Trigger.TriggerVisualState.Primed: activated + enum.Trigger.TriggerVisualState.Unprimed: complete + +- type: entity + id: HotPotatoEffect + noSpawn: true + components: + - type: TimedDespawn + lifetime: 0.6 + - type: Sprite + netsync: false + noRot: true + drawdepth: Effects + sprite: Effects/chemsmoke.rsi + state: chemsmoke + scale: "0.15, 0.15" + - type: EffectVisuals + - type: Tag + tags: + - HideContextMenu + - type: AnimationPlayer diff --git a/Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/activated.png b/Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/activated.png new file mode 100644 index 0000000000000000000000000000000000000000..07785ce832f0d731e711220ad72392434e4e52be GIT binary patch literal 1318 zcmV+>1=;$EP)mrTkwQ42a>7D1(_kF)l&wK~8m~G_zno-C(c`q>$)n{hs^twkh z+6d9iP|EP#v3NYjzw7OqDpju{s@MPb{#=cVrf)!1z3{N%dU;`)hS*BpRlFhGYGhN3 z!h6Dfo(w2_uJm-79~5WG{G!-PCdNTiJ@w2FPXAyd>*a)dg}ZWg!T6p>eM!i4rl+KK z$nL}^!30rs(iB+2CQn4FBboi}>;LjivMe4$-Y!Kvab(Dfw@=i9bTy?~qMP#A6jkoO z^YpF3fpYgy6l(8-;JF*>HsPZb1n>Jn@NoducW~-d{L7Nl$gAk3iq^FY;Waout7z#h zI9Z4GTQ}vUzNBi|nq|S$5j4$0%Oy0-m$Ujl=kNxStMZF3V0#C8BH~fLv;OnP%QV{)VVv%m&y-9Ipinca4 zC>#nNJ*&>PDz0EmN7i@lBj z0hx#R#L{n$+1zMj2!jvHb#FrNX0Z<9Ak<`a5FjA)5Wkj8u$!7HGehv+E7cWWcd5WT zGJmeRnMHH73=hBn1lVoGO=>5S1FMt6Eu)9k>Y8hD08b9yA z_Zhp$y3OI_5=X|fH6h+w6s1VEg$Cx3bxWC@OB@-`)`a+y!e!d4iEOKhG=BkPy|LpS zi-)U)*)Qx-SajZx{oNGC0l_tzr1_aBuRY2GS+AFYN|wX(c8N#m0WU9j(+d2ffOD4~Qn z``shj@pWwwCwhgmj)F=7)z~*x8}I~jSv*R$3k5IuuxoU03n(X4U3ZZ#vIi5+^gHC@T13yYYiYo6QW^J z^0TW}>Tv}P#n(IoL@F=ln0V8RuF`V&qU-DfNkOU+FNk9^i1SA1T c;3O0H13fm5%;vksga7~l07*qoM6N<$f~#qP4*&oF literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/icon.png b/Resources/Textures/Objects/Weapons/Bombs/hot_potato.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..426106d04691580dffbb759f3d57de24fca1c815 GIT binary patch literal 533 zcmV+w0_y#VP)i+SWW_wIg&C+a-`8KMv^KiP*aWRazrE4Oqt}`r&gy-LeyS*1QW9=RP8>A zqy&IH=mb`PgG0x^wg(-GY8In0W7wZA4Y2eNjGbOUzH$9VAcXD8RLTm^jJI5tzO%ip zVh=i$QVv6JsXu%Z+Jg@Jho5;_KMw3~q&$rMV>WYV06cm6ib=1_us`L&-try`)r4j( zEi$=0??y?kt)B+&9rae&%$;F0X57AciPtac0HNfe1I$AJWO8|bZ!R?&GfF83%T?HY zY(<)^P*VV4xeBspVY!MgE2SKa#?N)wpR&8Rc;tRZ