Add hot potato (#14204)

Co-authored-by: AJCM <AJCM@tutanota.com>
This commit is contained in:
Slava0135
2023-04-22 14:40:36 +03:00
committed by GitHub
parent 1907b249d7
commit c00bd9c247
14 changed files with 274 additions and 0 deletions

View File

@@ -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<ActiveHotPotatoComponent>();
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)));
}
}
}

View File

@@ -36,6 +36,12 @@ namespace Content.Server.Explosion.EntitySystems
}
}
/// <summary>
/// Raised when timer trigger becomes active.
/// </summary>
[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<AppearanceComponent>(uid, out var appearance))
_appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance);
}

View File

@@ -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<HotPotatoComponent, ActiveTimerTriggerEvent>(OnActiveTimer);
SubscribeLocalEvent<HotPotatoComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnActiveTimer(EntityUid uid, HotPotatoComponent comp, ref ActiveTimerTriggerEvent args)
{
EnsureComp<ActiveHotPotatoComponent>(uid);
comp.CanTransfer = false;
Dirty(comp);
}
private void OnMeleeHit(EntityUid uid, HotPotatoComponent comp, MeleeHitEvent args)
{
if (!HasComp<ActiveHotPotatoComponent>(uid))
return;
comp.CanTransfer = true;
foreach (var hitEntity in args.HitEntities)
{
if (!TryComp<HandsComponent>(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);
}
}

View File

@@ -104,6 +104,29 @@ public abstract partial class SharedHandsSystem : EntitySystem
return true;
}
/// <summary>
/// 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
/// </summary>
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))

View File

@@ -0,0 +1,23 @@
using Robust.Shared.GameStates;
namespace Content.Shared.HotPotato;
/// <summary>
/// Added to an activated hot potato. Controls hot potato transfer on server / effect spawning on client.
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedHotPotatoSystem))]
public sealed class ActiveHotPotatoComponent : Component
{
/// <summary>
/// Hot potato effect spawn cooldown in seconds
/// </summary>
[DataField("effectCooldown"), ViewVariables(VVAccess.ReadWrite)]
public float EffectCooldown = 0.3f;
/// <summary>
/// Moment in time next effect will be spawned
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan TargetTime = TimeSpan.Zero;
}

View File

@@ -0,0 +1,20 @@
using Robust.Shared.GameStates;
namespace Content.Shared.HotPotato;
/// <summary>
/// Similar to <see cref="Content.Shared.Interaction.Components.UnremoveableComponent"/>
/// except entities with this component can be removed in specific case: <see cref="CanTransfer"/>
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(SharedHotPotatoSystem))]
public sealed partial class HotPotatoComponent : Component
{
/// <summary>
/// If set to true entity can be removed by hitting entities if they have hands
/// </summary>
[DataField("canTransfer"), ViewVariables(VVAccess.ReadWrite)]
[AutoNetworkedField]
public bool CanTransfer = true;
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.Containers;
namespace Content.Shared.HotPotato;
public abstract class SharedHotPotatoSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HotPotatoComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
}
private void OnRemoveAttempt(EntityUid uid, HotPotatoComponent comp, ContainerGettingRemovedAttemptEvent args)
{
if (!comp.CanTransfer)
args.Cancel();
}
}

View File

@@ -0,0 +1,2 @@
hot-potato-passed = {$from} passed hot potato to {$to}!
hot-potato-failed = Can't pass the potato to {$to}!

View File

@@ -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!

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

View File

@@ -0,0 +1,23 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/commit/1dbcf389b0ec6b2c51b002df5fef8dd1519f8068",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "activated",
"delays": [
[
0.1,
0.1
]
]
}
]
}