This commit is contained in:
Alex Evgrashin
2022-04-15 01:00:50 +03:00
committed by GitHub
parent e769ad2725
commit fe4dbfd2f6
18 changed files with 556 additions and 10 deletions

View File

@@ -201,6 +201,7 @@ namespace Content.Client.Entry
"GasAnalyzable", "GasAnalyzable",
"GasCanister", "GasCanister",
"GasPort", "GasPort",
"Sticky",
"GasPortable", "GasPortable",
"AtmosPipeColor", "AtmosPipeColor",
"AtmosUnsafeUnanchor", "AtmosUnsafeUnanchor",

View File

@@ -0,0 +1,36 @@
using Content.Shared.Sticky.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Sticky.Visualizers;
public sealed class StickyVisualizerSystem : VisualizerSystem<StickyVisualizerComponent>
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StickyVisualizerComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, StickyVisualizerComponent component, ComponentInit args)
{
if (!TryComp(uid, out SpriteComponent? sprite))
return;
component.DefaultDrawDepth = sprite.DrawDepth;
}
protected override void OnAppearanceChange(EntityUid uid, StickyVisualizerComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);
if (!TryComp(uid, out SpriteComponent? sprite))
return;
if (!args.Component.TryGetData(StickyVisuals.IsStuck, out bool isStuck))
return;
var drawDepth = isStuck ? component.StuckDrawDepth : component.DefaultDrawDepth;
sprite.DrawDepth = drawDepth;
}
}

View File

@@ -32,5 +32,12 @@ namespace Content.Server.Explosion.Components
[DataField("beepParams")] [DataField("beepParams")]
public AudioParams BeepParams = AudioParams.Default.WithVolume(-2f); public AudioParams BeepParams = AudioParams.Default.WithVolume(-2f);
/// <summary>
/// Should timer be started when it was stuck to another entity.
/// Used for C4 charges and similar behaviour.
/// </summary>
[DataField("startOnStick")]
public bool StartOnStick;
} }
} }

View File

@@ -1,4 +1,5 @@
using Content.Server.Explosion.Components; using Content.Server.Explosion.Components;
using Content.Server.Sticky.Events;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
@@ -16,6 +17,22 @@ public sealed partial class TriggerSystem
SubscribeLocalEvent<OnUseTimerTriggerComponent, UseInHandEvent>(OnTimerUse); SubscribeLocalEvent<OnUseTimerTriggerComponent, UseInHandEvent>(OnTimerUse);
SubscribeLocalEvent<OnUseTimerTriggerComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<OnUseTimerTriggerComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<OnUseTimerTriggerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs); SubscribeLocalEvent<OnUseTimerTriggerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
SubscribeLocalEvent<OnUseTimerTriggerComponent, EntityStuckEvent>(OnStuck);
}
private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, EntityStuckEvent args)
{
if (!component.StartOnStick)
return;
HandleTimerTrigger(
uid,
args.User,
component.Delay,
component.BeepInterval,
component.InitialBeepDelay,
component.BeepSound,
component.BeepParams);
} }
private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args) private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args)

View File

@@ -4,6 +4,7 @@ using Content.Server.Doors.Systems;
using Content.Server.Explosion.Components; using Content.Server.Explosion.Components;
using Content.Server.Flash; using Content.Server.Flash;
using Content.Server.Flash.Components; using Content.Server.Flash.Components;
using Content.Server.Sticky.Events;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Physics; using Robust.Shared.Physics;

View File

@@ -0,0 +1,76 @@
using Content.Shared.Whitelist;
namespace Content.Server.Sticky.Components;
/// <summary>
/// Items that can be stick to other structures or entities.
/// For example paper stickers or C4 charges.
/// </summary>
[RegisterComponent]
public sealed class StickyComponent : Component
{
/// <summary>
/// What target entities are valid to be surface for sticky entity.
/// </summary>
[DataField("whitelist")]
[ViewVariables(VVAccess.ReadWrite)]
public EntityWhitelist? Whitelist;
/// <summary>
/// How much time does it take to stick entity to target.
/// If zero will stick entity immediately.
/// </summary>
[DataField("stickDelay")]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan StickDelay = TimeSpan.Zero;
/// <summary>
/// Whether users can unstick item when it was stuck to surface.
/// </summary>
[DataField("canUnstick")]
[ViewVariables(VVAccess.ReadWrite)]
public bool CanUnstick = true;
/// <summary>
/// How much time does it take to unstick entity.
/// If zero will unstick entity immediately.
/// </summary>
[DataField("unstickDelay")]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan UnstickDelay = TimeSpan.Zero;
/// <summary>
/// Popup message shown when player started sticking entity to another entity.
/// </summary>
[DataField("stickPopupStart")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StickPopupStart;
/// <summary>
/// Popup message shown when player successfully stuck entity.
/// </summary>
[DataField("stickPopupSuccess")]
[ViewVariables(VVAccess.ReadWrite)]
public string? StickPopupSuccess;
/// <summary>
/// Popup message shown when player started unsticking entity from another entity.
/// </summary>
[DataField("unstickPopupStart")]
[ViewVariables(VVAccess.ReadWrite)]
public string? UnstickPopupStart;
/// <summary>
/// Popup message shown when player successfully unstuck entity.
/// </summary>
[DataField("unstickPopupSuccess")]
[ViewVariables(VVAccess.ReadWrite)]
public string? UnstickPopupSuccess;
/// <summary>
/// Entity that is used as surface for sticky entity.
/// Null if entity doesn't stuck to anything.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? StuckTo;
}

View File

@@ -0,0 +1,45 @@
namespace Content.Server.Sticky.Events;
/// <summary>
/// Risen on sticky entity when it was stuck to other entity.
/// </summary>
public sealed class EntityStuckEvent : EntityEventArgs
{
/// <summary>
/// Entity that was used as a surface for sticky object.
/// </summary>
public readonly EntityUid Target;
/// <summary>
/// Entity that stuck sticky object on target.
/// </summary>
public readonly EntityUid User;
public EntityStuckEvent(EntityUid target, EntityUid user)
{
Target = target;
User = user;
}
}
/// <summary>
/// Risen on sticky entity when it was unstuck from other entity.
/// </summary>
public sealed class EntityUnstuckEvent : EntityEventArgs
{
/// <summary>
/// Entity that was used as a surface for sticky object.
/// </summary>
public readonly EntityUid Target;
/// <summary>
/// Entity that unstuck sticky object on target.
/// </summary>
public readonly EntityUid User;
public EntityUnstuckEvent(EntityUid target, EntityUid user)
{
Target = target;
User = user;
}
}

View File

@@ -0,0 +1,231 @@
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Sticky.Components;
using Content.Server.Sticky.Events;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Sticky.Components;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Player;
namespace Content.Server.Sticky.Systems;
public sealed class StickySystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
private const string StickerSlotId = "stickers_container";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StickSuccessfulEvent>(OnStickSuccessful);
SubscribeLocalEvent<UnstickSuccessfulEvent>(OnUnstickSuccessful);
SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(AddUnstickVerb);
}
private void OnAfterInteract(EntityUid uid, StickyComponent component, AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target == null)
return;
// try stick object to a clicked target entity
args.Handled = StartSticking(uid, args.User, args.Target.Value, component);
}
private void AddUnstickVerb(EntityUid uid, StickyComponent component, GetVerbsEvent<Verb> args)
{
if (component.StuckTo == null || !component.CanUnstick)
return;
args.Verbs.Add(new Verb
{
Text = Loc.GetString("comp-sticky-unstick-verb-text"),
IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
Act = () => StartUnsticking(uid, args.User, component)
});
}
private bool StartSticking(EntityUid uid, EntityUid user, EntityUid target, StickyComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (component.Whitelist != null && !component.Whitelist.IsValid(target))
return false;
// check if delay is not zero to start do after
var delay = (float) component.StickDelay.TotalSeconds;
if (delay > 0)
{
// show message to user
if (component.StickPopupStart != null)
{
var msg = Loc.GetString(component.StickPopupStart);
_popupSystem.PopupEntity(msg, user, Filter.Entities(user));
}
// start sticking object to target
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target)
{
BroadcastFinishedEvent = new StickSuccessfulEvent(uid, user, target),
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
});
}
else
{
// if delay is zero - stick entity immediately
StickToEntity(uid, target, user, component);
}
return true;
}
private void OnStickSuccessful(StickSuccessfulEvent ev)
{
// check if entity still has sticky component
if (!TryComp(ev.Uid, out StickyComponent? component))
return;
StickToEntity(ev.Uid, ev.Target, ev.User, component);
}
private void StartUnsticking(EntityUid uid, EntityUid user, StickyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var delay = (float) component.UnstickDelay.TotalSeconds;
if (delay > 0)
{
// show message to user
if (component.UnstickPopupStart != null)
{
var msg = Loc.GetString(component.UnstickPopupStart);
_popupSystem.PopupEntity(msg, user, Filter.Entities(user));
}
// start unsticking object
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: uid)
{
BroadcastFinishedEvent = new UnstickSuccessfulEvent(uid, user),
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true
});
}
else
{
// if delay is zero - unstick entity immediately
UnstickFromEntity(uid, user, component);
}
return;
}
private void OnUnstickSuccessful(UnstickSuccessfulEvent ev)
{
// check if entity still has sticky component
if (!TryComp(ev.Uid, out StickyComponent? component))
return;
UnstickFromEntity(ev.Uid, ev.User, component);
}
public void StickToEntity(EntityUid uid, EntityUid target, EntityUid user, StickyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
// add container to entity and insert sticker into it
var container = _containerSystem.EnsureContainer<Container>(target, StickerSlotId);
container.ShowContents = true;
if (!container.Insert(uid))
return;
// show message to user
if (component.StickPopupSuccess != null)
{
var msg = Loc.GetString(component.StickPopupSuccess);
_popupSystem.PopupEntity(msg, user, Filter.Entities(user));
}
// send information to appearance that entity is stuck
if (TryComp(uid, out AppearanceComponent? appearance))
{
appearance.SetData(StickyVisuals.IsStuck, true);
}
component.StuckTo = target;
RaiseLocalEvent(uid, new EntityStuckEvent(target, user));
}
public void UnstickFromEntity(EntityUid uid, EntityUid user, StickyComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (component.StuckTo == null)
return;
// try to remove sticky item from target container
var target = component.StuckTo.Value;
if (!_containerSystem.TryGetContainer(target, StickerSlotId, out var container) || !container.Remove(uid))
return;
// delete container if it's now empty
if (container.ContainedEntities.Count == 0)
container.Shutdown();
// try place dropped entity into user hands
_handsSystem.PickupOrDrop(user, uid);
// send information to appearance that entity isn't stuck
if (TryComp(uid, out AppearanceComponent? appearance))
{
appearance.SetData(StickyVisuals.IsStuck, false);
}
// show message to user
if (component.UnstickPopupSuccess != null)
{
var msg = Loc.GetString(component.UnstickPopupSuccess);
_popupSystem.PopupEntity(msg, user, Filter.Entities(user));
}
component.StuckTo = null;
RaiseLocalEvent(uid, new EntityUnstuckEvent(target, user));
}
private sealed class StickSuccessfulEvent : EntityEventArgs
{
public readonly EntityUid Uid;
public readonly EntityUid User;
public readonly EntityUid Target;
public StickSuccessfulEvent(EntityUid uid, EntityUid user, EntityUid target)
{
Uid = uid;
User = user;
Target = target;
}
}
private sealed class UnstickSuccessfulEvent : EntityEventArgs
{
public readonly EntityUid Uid;
public readonly EntityUid User;
public UnstickSuccessfulEvent(EntityUid uid, EntityUid user)
{
Uid = uid;
User = user;
}
}
}

View File

@@ -0,0 +1,27 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Sticky.Components;
using DrawDepth;
[RegisterComponent]
public sealed class StickyVisualizerComponent : Component
{
/// <summary>
/// What sprite draw depth set when entity stuck.
/// </summary>
[DataField("stuckDrawDepth")]
[ViewVariables(VVAccess.ReadWrite)]
public int StuckDrawDepth = (int) DrawDepth.Overdoors;
/// <summary>
/// What sprite draw depth set when entity unstuck.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int DefaultDrawDepth;
}
[Serializable, NetSerializable]
public enum StickyVisuals : byte
{
IsStuck
}

View File

@@ -0,0 +1,9 @@
# Bomb planting strings
comp-sticky-start-stick-bomb = You start planting the bomb...
comp-sticky-success-stick-bomb = You planted the bomb
comp-sticky-start-unstick-bomb = You start carefully removing the bomb...
comp-sticky-success-unstick-bomb = You removed the bomb
# General strings
comp-sticky-unstick-verb-text = Unstick

View File

@@ -169,6 +169,17 @@
- id: PlushieNuke - id: PlushieNuke
- id: PlushieLizard - id: PlushieLizard
- type: entity
parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateC4tBundle
name: syndicate C-4 bundle
description: "Contains a lot of C-4 charges."
components:
- type: StorageFill
contents:
- id: C4
amount: 8
- type: entity - type: entity
parent: ClothingBackpackDuffelSyndicate parent: ClothingBackpackDuffelSyndicate
id: ClothingBackpackDuffelSyndicateHardsuitBundle id: ClothingBackpackDuffelSyndicateHardsuitBundle

View File

@@ -90,17 +90,21 @@
itemId: MobGrenadePenguin itemId: MobGrenadePenguin
price: 6 price: 6
#- type: uplinkListing - type: uplinkListing
# id: UplinkExplosiveC4 id: UplinkC4
# category: Weapons category: Explosives
# itemId: ExplosiveC4 itemId: C4
# price: 5 price: 2
description: >
C-4 is plastic explosive of the common variety Composition C. You can use it to breach walls, airlocks or sabotage equipment.
It can be attached to almost all objects and has a modifiable timer with a minimum setting of 10 seconds.
#- type: uplinkListing - type: uplinkListing
# id: UplinkDuffelExplosiveC4 id: UplinkC4Bundle
# category: Weapons category: Explosives
# itemId: DuffelExplosiveC4 itemId: ClothingBackpackDuffelSyndicateC4tBundle
# price: 15 price: 12 # 25% off
description: Because sometimes quantity is quality. Contains 8 C-4 plastic explosives.
# Ammo # Ammo

View File

@@ -0,0 +1,50 @@
- type: entity
name: composition C-4
description: Used to put holes in specific areas without too much extra hole. A saboteur's favorite.
parent: BaseItem
id: C4
components:
- type: Sprite
sprite: Objects/Weapons/Bombs/c4.rsi
state: icon
- type: Item
sprite: Objects/Weapons/Bombs/c4.rsi
size: 10
- type: OnUseTimerTrigger
delay: 10
delayOptions: [10, 30, 60, 120, 300]
initialBeepDelay: 0
beepSound: /Audio/Machines/Nuke/general_beep.ogg
startOnStick: true
- type: Sticky
stickDelay: 5
unstickDelay: 5
stickPopupStart: comp-sticky-start-stick-bomb
stickPopupSuccess: comp-sticky-success-stick-bomb
unstickPopupStart: comp-sticky-start-unstick-bomb
unstickPopupSuccess: comp-sticky-success-unstick-bomb
- type: Explosive # Powerful explosion in a very small radius. Doesn't break underplating.
explosionType: Default
maxIntensity: 300
intensitySlope: 100
totalIntensity: 300
canCreateVacuum: false
- type: ExplodeOnTrigger
- type: Damageable
damageContainer: Inorganic
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 10
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: StickyVisualizer
- type: Appearance
visuals:
- type: GenericEnumVisualizer
key: enum.Trigger.TriggerVisuals.VisualState
states:
enum.Trigger.TriggerVisualState.Primed: primed
enum.Trigger.TriggerVisualState.Unprimed: complete

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

View File

@@ -0,0 +1,31 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/b13d244d761a07e200a9a41730bd446e776020d5",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "primed",
"delays": [
[
0.1,
0.1
]
]
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B