+++ /dev/null
-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
- {
- }
-}
+++ /dev/null
-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
- {
- /// <summary>
- /// Current state to matchstick. Can be <code>Unlit</code>, <code>Lit</code> or <code>Burnt</code>.
- /// </summary>
- [DataField("state")]
- public SmokableState CurrentState = SmokableState.Unlit;
-
- /// <summary>
- /// How long will matchstick last in seconds.
- /// </summary>
- [ViewVariables(VVAccess.ReadOnly)]
- [DataField("duration")]
- public int Duration = 10;
-
- /// <summary>
- /// Sound played when you ignite the matchstick.
- /// </summary>
- [DataField("igniteSound", required: true)] public SoundSpecifier IgniteSound = default!;
- }
-}
+++ /dev/null
-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<MatchboxComponent, InteractUsingEvent>(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;
- }
- }
- }
-}
+++ /dev/null
-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 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<Entity<MatchstickComponent>> _litMatches = new();
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<MatchstickComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<MatchstickComponent, IsHotEvent>(OnIsHotEvent);
- SubscribeLocalEvent<MatchstickComponent, ComponentShutdown>(OnShutdown);
- }
-
- private void OnShutdown(Entity<MatchstickComponent> 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;
-
- var position = _transformSystem.GetGridOrMapTilePosition(match, xform);
-
- _atmosphereSystem.HotspotExpose(gridUid, position, 400, 50, match, true);
- }
- }
-
- private void OnInteractUsing(Entity<MatchstickComponent> 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<MatchstickComponent> matchstick, EntityUid user)
- {
- var component = matchstick.Comp;
-
- // Play Sound
- _audio.PlayPvs(component.IgniteSound, matchstick, AudioParams.Default.WithVariation(0.125f).WithVolume(-0.125f));
-
- // 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);
- });
- }
-
- private 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);
- }
- }
- }
-}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.IgnitionSource.Components;
+
+/// <summary>
+/// Component for entities that light matches when they interact. (E.g. striking the match on the matchbox)
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class MatchboxComponent : Component;
+
--- /dev/null
+using Content.Shared.Smoking;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.IgnitionSource.Components;
+
+[NetworkedComponent, RegisterComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class MatchstickComponent : Component
+{
+ /// <summary>
+ /// Current state to matchstick. Can be <code>Unlit</code>, <code>Lit</code> or <code>Burnt</code>.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public SmokableState State = SmokableState.Unlit;
+
+ /// <summary>
+ /// How long the matchstick will burn for.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(10);
+
+ /// <summary>
+ /// The time that the match will burn out. If null, that means the match is unlit.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
+ public TimeSpan? TimeMatchWillBurnOut;
+
+ /// <summary>
+ /// Sound played when you ignite the matchstick.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier? IgniteSound;
+}
--- /dev/null
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.IgnitionSource.Components;
+
+namespace Content.Shared.IgnitionSource.EntitySystems;
+
+public sealed class MatchboxSystem : EntitySystem
+{
+ [Dependency] private readonly MatchstickSystem _match = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<MatchboxComponent, InteractUsingEvent>(OnInteractUsing, before: [ typeof(SharedStorageSystem) ]);
+ }
+
+ private void OnInteractUsing(Entity<MatchboxComponent> ent, ref InteractUsingEvent args)
+ {
+ if (args.Handled || !TryComp<MatchstickComponent>(args.Used, out var matchstick))
+ return;
+
+ args.Handled = _match.TryIgnite((args.Used, matchstick), args.User);
+ }
+}
--- /dev/null
+using Content.Shared.Interaction;
+using Content.Shared.Item;
+using Content.Shared.Smoking;
+using Content.Shared.Temperature;
+using Robust.Shared.Audio.Systems;
+using Content.Shared.IgnitionSource.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.IgnitionSource.EntitySystems;
+
+public sealed partial class MatchstickSystem : 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!;
+ [Dependency] private readonly SharedIgnitionSourceSystem _ignition = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<MatchstickComponent, InteractUsingEvent>(OnInteractUsing);
+ }
+
+ // This is for something *else* lighting the matchstick, not the matchstick lighting something else.
+ private void OnInteractUsing(Entity<MatchstickComponent> ent, ref InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ var isHotEvent = new IsHotEvent();
+ RaiseLocalEvent(args.Used, isHotEvent);
+
+ if (!isHotEvent.IsHot)
+ return;
+
+ args.Handled = TryIgnite(ent, args.User);
+ }
+
+ /// <summary>
+ /// Try to light a matchstick!
+ /// </summary>
+ /// <param name="matchstick">The matchstick to light.</param>
+ /// <param name="user">The user lighting the matchstick can be null if there isn't any user.</param>
+ /// <returns>True if the matchstick was lit, false otherwise.</returns>
+ public bool TryIgnite(Entity<MatchstickComponent> matchstick, EntityUid? user)
+ {
+ if (matchstick.Comp.State != SmokableState.Unlit)
+ return false;
+
+ // Play Sound
+ _audio.PlayPredicted(matchstick.Comp.IgniteSound, matchstick, user);
+
+ // Change state
+ SetState(matchstick, SmokableState.Lit);
+ matchstick.Comp.TimeMatchWillBurnOut = _timing.CurTime + matchstick.Comp.Duration;
+
+ Dirty(matchstick);
+
+ return true;
+ }
+
+ private void SetState(Entity<MatchstickComponent> ent, SmokableState newState)
+ {
+ _lights.SetEnabled(ent, newState == SmokableState.Lit);
+
+ _appearance.SetData(ent, SmokingVisuals.Smoking, newState);
+
+ _ignition.SetIgnited(ent.Owner, newState == SmokableState.Lit);
+
+ switch (newState)
+ {
+ case SmokableState.Lit:
+ _item.SetHeldPrefix(ent, "lit");
+ break;
+ default:
+ _item.SetHeldPrefix(ent, "unlit");
+ break;
+ }
+
+ ent.Comp.State = newState;
+ Dirty(ent);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<MatchstickComponent>();
+
+ while (query.MoveNext(out var uid, out var match))
+ {
+ if (match.State != SmokableState.Lit)
+ continue;
+
+ // Check if the match has expired.
+ if (_timing.CurTime > match.TimeMatchWillBurnOut)
+ SetState((uid, match), SmokableState.Burnt);
+ }
+ }
+}
duration: 10
igniteSound:
path: /Audio/Items/match_strike.ogg
+ params:
+ volume: -0.125
+ variation: 0.125
+ - type: IgnitionSource
+ ignited: false
+ temperature: 400.0
- type: PointLight
enabled: false
radius: 1.1