diff --git a/Content.Client/Lightning/ClientMatchstickSystem.cs b/Content.Client/Lightning/ClientMatchstickSystem.cs
new file mode 100644
index 00000000000000..c05abb8ccc34bf
--- /dev/null
+++ b/Content.Client/Lightning/ClientMatchstickSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Light.EntitySystems;
+
+namespace Content.Client.Light.EntitySystems;
+
+public sealed class MatchstickSystem : SharedMatchstickSystem
+{
+}
diff --git a/Content.Server/Light/Components/MatchboxComponent.cs b/Content.Server/Light/Components/MatchboxComponent.cs
deleted file mode 100644
index 12cd4e38808169..00000000000000
--- a/Content.Server/Light/Components/MatchboxComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Light.Components
-{
- // TODO make changes in icons when different threshold reached
- // e.g. different icons for 10% 50% 100%
- [RegisterComponent]
- public sealed partial class MatchboxComponent : Component
- {
- }
-}
diff --git a/Content.Server/Light/Components/MatchstickComponent.cs b/Content.Server/Light/Components/MatchstickComponent.cs
deleted file mode 100644
index 3c47f4c18b3831..00000000000000
--- a/Content.Server/Light/Components/MatchstickComponent.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Content.Server.Light.EntitySystems;
-using Content.Shared.Smoking;
-using Robust.Shared.Audio;
-
-namespace Content.Server.Light.Components
-{
- [RegisterComponent]
- [Access(typeof(MatchstickSystem))]
- public sealed partial class MatchstickComponent : Component
- {
- ///
- /// Current state to matchstick. Can be Unlit
, Lit
or Burnt
.
- ///
- [DataField("state")]
- public SmokableState CurrentState = SmokableState.Unlit;
-
- ///
- /// How long will matchstick last in seconds.
- ///
- [ViewVariables(VVAccess.ReadOnly)]
- [DataField("duration")]
- public int Duration = 10;
-
- ///
- /// Sound played when you ignite the matchstick.
- ///
- [DataField("igniteSound", required: true)] public SoundSpecifier IgniteSound = default!;
- }
-}
diff --git a/Content.Server/Light/EntitySystems/MatchboxSystem.cs b/Content.Server/Light/EntitySystems/MatchboxSystem.cs
deleted file mode 100644
index 9a73e44f8783af..00000000000000
--- a/Content.Server/Light/EntitySystems/MatchboxSystem.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Content.Server.Light.Components;
-using Content.Server.Storage.EntitySystems;
-using Content.Shared.Interaction;
-using Content.Shared.Smoking;
-
-namespace Content.Server.Light.EntitySystems
-{
- public sealed class MatchboxSystem : EntitySystem
- {
- [Dependency] private readonly MatchstickSystem _stickSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnInteractUsing, before: new[] { typeof(StorageSystem) });
- }
-
- private void OnInteractUsing(EntityUid uid, MatchboxComponent component, InteractUsingEvent args)
- {
- if (!args.Handled
- && EntityManager.TryGetComponent(args.Used, out MatchstickComponent? matchstick)
- && matchstick.CurrentState == SmokableState.Unlit)
- {
- _stickSystem.Ignite((args.Used, matchstick), args.User);
- args.Handled = true;
- }
- }
- }
-}
diff --git a/Content.Server/Light/EntitySystems/MatchstickSystem.cs b/Content.Server/Light/EntitySystems/MatchstickSystem.cs
index 96e4695784dd4f..25f137611d5475 100644
--- a/Content.Server/Light/EntitySystems/MatchstickSystem.cs
+++ b/Content.Server/Light/EntitySystems/MatchstickSystem.cs
@@ -1,123 +1,47 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Light.Components;
-using Content.Shared.Audio;
-using Content.Shared.Interaction;
-using Content.Shared.Item;
using Content.Shared.Smoking;
-using Content.Shared.Temperature;
+using Content.Shared.Light.Components;
+using Content.Server.Atmos.EntitySystems;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Player;
-
-namespace Content.Server.Light.EntitySystems
-{
- public sealed class MatchstickSystem : EntitySystem
- {
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedItemSystem _item = default!;
- [Dependency] private readonly SharedPointLightSystem _lights = default!;
- [Dependency] private readonly TransformSystem _transformSystem = default!;
-
- private readonly HashSet> _litMatches = new();
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnInteractUsing);
- SubscribeLocalEvent(OnIsHotEvent);
- SubscribeLocalEvent(OnShutdown);
- }
-
- private void OnShutdown(Entity ent, ref ComponentShutdown args)
- {
- _litMatches.Remove(ent);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- foreach (var match in _litMatches)
- {
- if (match.Comp.CurrentState != SmokableState.Lit || Paused(match) || match.Comp.Deleted)
- continue;
-
- var xform = Transform(match);
-
- if (xform.GridUid is not {} gridUid)
- return;
+using Robust.Shared.Timing;
+using Content.Shared.Light.EntitySystems;
- var position = _transformSystem.GetGridOrMapTilePosition(match, xform);
+namespace Content.Server.Light.EntitySystems;
- _atmosphereSystem.HotspotExpose(gridUid, position, 400, 50, match, true);
- }
- }
-
- private void OnInteractUsing(Entity ent, ref InteractUsingEvent args)
- {
- if (args.Handled || ent.Comp.CurrentState != SmokableState.Unlit)
- return;
-
- var isHotEvent = new IsHotEvent();
- RaiseLocalEvent(args.Used, isHotEvent);
-
- if (!isHotEvent.IsHot)
- return;
+public sealed class MatchstickSystem : SharedMatchstickSystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly TransformSystem _transformSystem = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
- Ignite(ent, args.User);
- args.Handled = true;
- }
+ public override void Initialize()
+ {
+ base.Initialize();
+ }
- private void OnIsHotEvent(EntityUid uid, MatchstickComponent component, IsHotEvent args)
- {
- args.IsHot = component.CurrentState == SmokableState.Lit;
- }
+ public override void Update(float frameTime)
+ {
+ var query = EntityQueryEnumerator();
- public void Ignite(Entity matchstick, EntityUid user)
+ while (query.MoveNext(out var uid, out var match))
{
- var component = matchstick.Comp;
+ if (match.CurrentState != SmokableState.Lit)
+ continue;
- // Play Sound
- _audio.PlayPvs(component.IgniteSound, matchstick, AudioParams.Default.WithVariation(0.125f).WithVolume(-0.125f));
+ var xform = Transform(uid);
- // Change state
- SetState(matchstick, component, SmokableState.Lit);
- _litMatches.Add(matchstick);
- matchstick.Owner.SpawnTimer(component.Duration * 1000, delegate
- {
- SetState(matchstick, component, SmokableState.Burnt);
- _litMatches.Remove(matchstick);
- });
- }
+ if (xform.GridUid is not { } gridUid)
+ continue;
- private void SetState(EntityUid uid, MatchstickComponent component, SmokableState value)
- {
- component.CurrentState = value;
+ var position = _transformSystem.GetGridOrMapTilePosition(uid, xform);
- if (_lights.TryGetLight(uid, out var pointLightComponent))
- {
- _lights.SetEnabled(uid, component.CurrentState == SmokableState.Lit, pointLightComponent);
- }
-
- if (EntityManager.TryGetComponent(uid, out ItemComponent? item))
- {
- switch (component.CurrentState)
- {
- case SmokableState.Lit:
- _item.SetHeldPrefix(uid, "lit", component: item);
- break;
- default:
- _item.SetHeldPrefix(uid, "unlit", component: item);
- break;
- }
- }
+ _atmosphereSystem.HotspotExpose(gridUid, position, 400, 50, uid, true);
- if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
+ // Check if the match has expired.
+ var burnoutTime = match.TimeMatchWillBurnOut;
+ if (burnoutTime != null && _timing.CurTime > burnoutTime)
{
- _appearance.SetData(uid, SmokingVisuals.Smoking, component.CurrentState, appearance);
+ SetState(uid, match, SmokableState.Burnt);
+ match.TimeMatchWillBurnOut = null;
}
}
}
diff --git a/Content.Shared/Lightning/Components/MatchboxComponent.cs b/Content.Shared/Lightning/Components/MatchboxComponent.cs
new file mode 100644
index 00000000000000..8747c231c0babb
--- /dev/null
+++ b/Content.Shared/Lightning/Components/MatchboxComponent.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Light.Components;
+
+// TODO make changes in icons when different threshold reached
+// e.g. different icons for 10% 50% 100%
+[RegisterComponent, NetworkedComponent]
+public sealed partial class MatchboxComponent : Component
+{
+}
+
diff --git a/Content.Shared/Lightning/Components/MatchstickComponent.cs b/Content.Shared/Lightning/Components/MatchstickComponent.cs
new file mode 100644
index 00000000000000..e741b5d9aa4c01
--- /dev/null
+++ b/Content.Shared/Lightning/Components/MatchstickComponent.cs
@@ -0,0 +1,35 @@
+using Content.Shared.Smoking;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Light.Components;
+
+[NetworkedComponent, RegisterComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class MatchstickComponent : Component
+{
+ ///
+ /// Current state to matchstick. Can be Unlit
, Lit
or Burnt
.
+ ///
+ [DataField("state")]
+ [AutoNetworkedField]
+ public SmokableState CurrentState = SmokableState.Unlit;
+
+ ///
+ /// How long will matchstick last in seconds.
+ ///
+ [DataField("duration")]
+ public int Duration = 10;
+
+ ///
+ /// The time that the match will burn out. If null, that means the match is unlit.
+ ///
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan? TimeMatchWillBurnOut = null;
+
+ ///
+ /// Sound played when you ignite the matchstick.
+ ///
+ [DataField("igniteSound", required: true)] public SoundSpecifier IgniteSound = default!;
+}
diff --git a/Content.Shared/Lightning/EntitySystems/MatchboxSystem.cs b/Content.Shared/Lightning/EntitySystems/MatchboxSystem.cs
new file mode 100644
index 00000000000000..7bcbc654206df2
--- /dev/null
+++ b/Content.Shared/Lightning/EntitySystems/MatchboxSystem.cs
@@ -0,0 +1,28 @@
+using Content.Shared.Light.Components;
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Smoking;
+
+namespace Content.Shared.Light.EntitySystems;
+
+public sealed class SharedMatchboxSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMatchstickSystem _stickSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInteractUsing, before: new[] { typeof(SharedStorageSystem) });
+ }
+
+ private void OnInteractUsing(EntityUid uid, MatchboxComponent component, InteractUsingEvent args)
+ {
+ if (args.Handled || !EntityManager.TryGetComponent(args.Used, out MatchstickComponent? matchstick))
+ return;
+
+ if (matchstick.CurrentState == SmokableState.Unlit)
+ _stickSystem.Ignite((args.Used, matchstick), args.User);
+
+ args.Handled = true;
+ }
+}
diff --git a/Content.Shared/Lightning/EntitySystems/SharedMatchstickSystem.cs b/Content.Shared/Lightning/EntitySystems/SharedMatchstickSystem.cs
new file mode 100644
index 00000000000000..d7f80a978790ce
--- /dev/null
+++ b/Content.Shared/Lightning/EntitySystems/SharedMatchstickSystem.cs
@@ -0,0 +1,86 @@
+using Content.Shared.Interaction;
+using Content.Shared.Item;
+using Content.Shared.Smoking;
+using Content.Shared.Temperature;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Player;
+using Content.Shared.Light.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Light.EntitySystems;
+
+public abstract class SharedMatchstickSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedItemSystem _item = default!;
+ [Dependency] private readonly SharedPointLightSystem _lights = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInteractUsing);
+ SubscribeLocalEvent(OnIsHotEvent);
+ }
+
+ private void OnInteractUsing(Entity ent, ref InteractUsingEvent args)
+ {
+ if (args.Handled || ent.Comp.CurrentState != SmokableState.Unlit)
+ return;
+
+ var isHotEvent = new IsHotEvent();
+ RaiseLocalEvent(args.Used, isHotEvent);
+
+ if (!isHotEvent.IsHot)
+ return;
+
+ Ignite(ent, args.User);
+ args.Handled = true;
+ }
+
+ private void OnIsHotEvent(EntityUid uid, MatchstickComponent component, IsHotEvent args)
+ {
+ args.IsHot = component.CurrentState == SmokableState.Lit;
+ }
+
+ public void Ignite(Entity matchstick, EntityUid user)
+ {
+ // Play Sound
+ _audio.PlayPredicted(matchstick.Comp.IgniteSound, matchstick, user, AudioParams.Default.WithVariation(0.125f).WithVolume(-0.125f));
+
+ // Change state
+ SetState(matchstick, matchstick.Comp, SmokableState.Lit);
+ matchstick.Comp.TimeMatchWillBurnOut = _timing.CurTime + TimeSpan.FromSeconds(matchstick.Comp.Duration);
+
+ }
+
+ protected void SetState(EntityUid uid, MatchstickComponent component, SmokableState value)
+ {
+ component.CurrentState = value;
+
+ if (_lights.TryGetLight(uid, out var pointLightComponent))
+ {
+ _lights.SetEnabled(uid, component.CurrentState == SmokableState.Lit, pointLightComponent);
+ }
+
+ if (EntityManager.TryGetComponent(uid, out ItemComponent? item))
+ {
+ switch (component.CurrentState)
+ {
+ case SmokableState.Lit:
+ _item.SetHeldPrefix(uid, "lit", component: item);
+ break;
+ default:
+ _item.SetHeldPrefix(uid, "unlit", component: item);
+ break;
+ }
+ }
+
+ if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
+ {
+ _appearance.SetData(uid, SmokingVisuals.Smoking, component.CurrentState, appearance);
+ }
+ }
+}