PriorityTape,
Breakage,
Trash,
+ Bomb,
+ BombPrimed,
}
public enum DeliverySpawnerVisualLayers : byte
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Component given to deliveries.
+/// This delivery will "prime" based on circumstances defined in the datafield.
+/// When primed, it will attempt to explode every few seconds, with the chance increasing each time it fails to do so.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(DeliveryModifierSystem))]
+public sealed partial class DeliveryBombComponent : Component
+{
+ /// <summary>
+ /// How often will this bomb retry to explode.
+ /// </summary>
+ [DataField]
+ public TimeSpan ExplosionRetryDelay = TimeSpan.FromSeconds(5);
+
+ /// <summary>
+ /// The time at which the next retry will happen
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextExplosionRetry;
+
+ /// <summary>
+ /// The chance this bomb explodes each time it attempts to do so.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float ExplosionChance = 0.01f;
+
+ /// <summary>
+ /// How much should the chance of explosion increase each failed retry?
+ /// </summary>
+ [DataField]
+ public float ExplosionChanceRetryIncrease = 0.01f;
+
+ /// <summary>
+ /// Should this bomb get primed when the delivery is unlocked?
+ /// </summary>
+ [DataField]
+ public bool PrimeOnUnlock = true;
+
+ /// <summary>
+ /// Should this bomb get primed when the delivery is broken?
+ /// Requires to be fragile as well.
+ /// </summary>
+ [DataField]
+ public bool PrimeOnBreakage = true;
+
+ /// <summary>
+ /// Should this bomb get primed when the delivery expires?
+ /// Requires to be priority as well.
+ /// </summary>
+ [DataField]
+ public bool PrimeOnExpire = true;
+
+ /// <summary>
+ /// Multiplier to choose when a crazy person actually opens it.
+ /// Multiplicative, not additive.
+ /// </summary>
+ [DataField]
+ public float SpesoMultiplier = 1.5f;
+}
+using Content.Shared.Audio;
using Content.Shared.Destructible;
using Content.Shared.Examine;
+using Content.Shared.Explosion.EntitySystems;
using Content.Shared.NameModifier.EntitySystems;
+using JetBrains.Annotations;
+using Robust.Shared.Network;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly NameModifierSystem _nameModifier = default!;
[Dependency] private readonly SharedDeliverySystem _delivery = default!;
+ [Dependency] private readonly SharedExplosionSystem _explosion = default!;
+ [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
public override void Initialize()
{
SubscribeLocalEvent<DeliveryFragileComponent, BreakageEventArgs>(OnFragileBreakage);
SubscribeLocalEvent<DeliveryFragileComponent, ExaminedEvent>(OnFragileExamine);
SubscribeLocalEvent<DeliveryFragileComponent, GetDeliveryMultiplierEvent>(OnGetFragileMultiplier);
+
+ SubscribeLocalEvent<DeliveryBombComponent, ComponentStartup>(OnExplosiveStartup);
+ SubscribeLocalEvent<PrimedDeliveryBombComponent, MapInitEvent>(OnPrimedExplosiveMapInit);
+ SubscribeLocalEvent<DeliveryBombComponent, ExaminedEvent>(OnExplosiveExamine);
+ SubscribeLocalEvent<DeliveryBombComponent, GetDeliveryMultiplierEvent>(OnGetExplosiveMultiplier);
+ SubscribeLocalEvent<DeliveryBombComponent, DeliveryUnlockedEvent>(OnExplosiveUnlock);
+ SubscribeLocalEvent<DeliveryBombComponent, DeliveryPriorityExpiredEvent>(OnExplosiveExpire);
+ SubscribeLocalEvent<DeliveryBombComponent, BreakageEventArgs>(OnExplosiveBreak);
}
#region Random
}
#endregion
+ #region Explosive
+ private void OnExplosiveStartup(Entity<DeliveryBombComponent> ent, ref ComponentStartup args)
+ {
+ _delivery.UpdateBombVisuals(ent);
+ }
+
+ private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+ {
+ if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+ return;
+
+ bomb.NextExplosionRetry = _timing.CurTime;
+ }
+
+ private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
+ {
+ var trueName = _nameModifier.GetBaseName(ent.Owner);
+
+ var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+ if (isPrimed)
+ args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
+ else
+ args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
+ }
+
+ private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
+ {
+ // Big danger for big rewards
+ args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
+ }
+
+ private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
+ {
+ if (!ent.Comp.PrimeOnUnlock)
+ return;
+
+ PrimeBombDelivery(ent);
+ }
+
+ private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
+ {
+ if (!ent.Comp.PrimeOnExpire)
+ return;
+
+ PrimeBombDelivery(ent);
+ }
+
+ private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
+ {
+ if (!ent.Comp.PrimeOnBreakage)
+ return;
+
+ PrimeBombDelivery(ent);
+ }
+
+ [PublicAPI]
+ public void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
+ {
+ EnsureComp<PrimedDeliveryBombComponent>(ent);
+
+ _delivery.UpdateBombVisuals(ent);
+
+ _ambientSound.SetAmbience(ent, true);
+ }
+ #endregion
+
#region Update Loops
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdatePriorty(frameTime);
+ UpdateBomb(frameTime);
}
private void UpdatePriorty(float frameTime)
}
}
}
+
+ private void UpdateBomb(float frameTime)
+ {
+ var bombQuery = EntityQueryEnumerator<PrimedDeliveryBombComponent, DeliveryBombComponent>();
+ var curTime = _timing.CurTime;
+
+ while (bombQuery.MoveNext(out var uid, out _, out var bombData))
+ {
+ if (bombData.NextExplosionRetry > curTime)
+ continue;
+
+ bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+ // Explosions cannot be predicted.
+ if (_net.IsServer && _random.NextFloat() < bombData.ExplosionChance)
+ _explosion.TriggerExplosive(uid);
+
+ bombData.ExplosionChance += bombData.ExplosionChanceRetryIncrease;
+ Dirty(uid, bombData);
+ }
+ }
#endregion
}
IsTrash,
IsBroken,
IsFragile,
+ IsBomb,
PriorityState,
JobIcon,
}
Inactive,
}
+[Serializable, NetSerializable]
+public enum DeliveryBombState : byte
+{
+ Off,
+ Inactive,
+ Primed,
+}
+
[Serializable, NetSerializable]
public enum DeliverySpawnerVisuals : byte
{
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Component given to deliveries.
+/// Indicates this bomb delivery is primed.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(DeliveryModifierSystem))]
+public sealed partial class PrimedDeliveryBombComponent : Component;
_appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
}
+ public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+ {
+ var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+ _appearance.SetData(ent, DeliveryVisuals.IsBomb, isPrimed ? DeliveryBombState.Primed : DeliveryBombState.Inactive);
+ }
+
protected void UpdateDeliverySpawnerVisuals(EntityUid uid, int contents)
{
_appearance.SetData(uid, DeliverySpawnerVisuals.Contents, contents > 0);
delivery-fragile-examine = This is a [color=red]fragile {$type}[/color]. Deliver it intact for a bonus.
delivery-fragile-broken-examine = This is a [color=red]fragile {$type}[/color]. It looks badly damaged.
+
+delivery-bomb-examine = This is a [color=purple]bomb {$type}[/color]. Oh no.
+delivery-bomb-primed-examine = This is a [color=purple]bomb {$type}[/color]. Reading this is a bad use of your time.
enum.DeliveryVisualLayers.Trash:
True: { visible: true }
False: { visible: false }
+ enum.DeliveryVisuals.IsBomb:
+ enum.DeliveryVisualLayers.Bomb:
+ Off: { visible: false }
+ Inactive: { visible: true }
+ Primed: { visible: true }
+ enum.DeliveryVisualLayers.BombPrimed:
+ Off: { visible: false }
+ Inactive: { visible: false }
+ Primed: { visible: true }
- type: Label
examinable: false
- type: FingerprintReader
- state: broken
map: [ "enum.DeliveryVisualLayers.Breakage" ]
visible: false
+ - state: bomb
+ map: [ "enum.DeliveryVisualLayers.Bomb" ]
+ visible: false
+ - state: bomb_unshaded
+ map: [ "enum.DeliveryVisualLayers.BombPrimed" ]
+ shader: unshaded
+ visible: false
- type: MultiHandedItem
- type: Item
size: Huge
- state: broken
map: [ "enum.DeliveryVisualLayers.Breakage" ]
visible: false
+ - state: bomb
+ map: [ "enum.DeliveryVisualLayers.Bomb" ]
+ visible: false
+ - state: bomb_unshaded
+ map: [ "enum.DeliveryVisualLayers.BombPrimed" ]
+ shader: unshaded
+ visible: false
- type: Item
storedRotation: 90
- type: Delivery
prob: 0.25
- id: DeliveryModifierFragile
prob: 0.25
+ - id: DeliveryModifierBomb
+ prob: 0.02 # Should happen maybe once or twice per game.
- type: entity
id: DeliveryModifierPriority
collection: DeliveryOpenSounds
- !type:DoActsBehavior
acts: [ "Breakage" ]
+
+- type: entity
+ id: DeliveryModifierBomb
+ description: Components to add when a delivery is rolled as a bomb.
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: DeliveryBomb
+ - type: AmbientSound
+ enabled: false
+ range: 8
+ sound:
+ path: /Audio/Effects/lightburn.ogg
+ - type: Explosive
+ explosionType: MicroBomb
+ totalIntensity: 120
+ intensitySlope: 4
+ maxIntensity: 30
+ canCreateVacuum: false
"name": "bomb"
},
{
- "name": "bomb-unshaded",
+ "name": "bomb_unshaded",
"delays": [
[
0.2,
"name": "bomb"
},
{
- "name": "bomb-unshaded",
+ "name": "bomb_unshaded",
"delays": [
[
0.125,