From 3135b2ab3eb9ed65d16ec81b39f82e9c208cb6c8 Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Tue, 8 Apr 2025 14:00:57 -0700 Subject: [PATCH] Fix matchstick prediction issues (#31418) * First commit * Minor fixes please ymal error begone * If this fixes it * Last chance * How * Forgot * First fixes * Added correct component tags * Minor cleanup * Address review! * Namespace change * Fix yaml yelling * Changes * Update namespace * Removed the unneeded files --- .../Light/Components/MatchboxComponent.cs | 9 -- .../Light/Components/MatchstickComponent.cs | 29 ---- .../Light/EntitySystems/MatchboxSystem.cs | 29 ---- .../Light/EntitySystems/MatchstickSystem.cs | 124 ------------------ .../Components/MatchboxComponent.cs | 10 ++ .../Components/MatchstickComponent.cs | 34 +++++ .../EntitySystems/MatchboxSystem.cs | 25 ++++ .../EntitySystems/MatchstickSystem.cs | 102 ++++++++++++++ .../Entities/Objects/Tools/matches.yml | 6 + 9 files changed, 177 insertions(+), 191 deletions(-) delete mode 100644 Content.Server/Light/Components/MatchboxComponent.cs delete mode 100644 Content.Server/Light/Components/MatchstickComponent.cs delete mode 100644 Content.Server/Light/EntitySystems/MatchboxSystem.cs delete mode 100644 Content.Server/Light/EntitySystems/MatchstickSystem.cs create mode 100644 Content.Shared/IgnitionSource/Components/MatchboxComponent.cs create mode 100644 Content.Shared/IgnitionSource/Components/MatchstickComponent.cs create mode 100644 Content.Shared/IgnitionSource/EntitySystems/MatchboxSystem.cs create mode 100644 Content.Shared/IgnitionSource/EntitySystems/MatchstickSystem.cs diff --git a/Content.Server/Light/Components/MatchboxComponent.cs b/Content.Server/Light/Components/MatchboxComponent.cs deleted file mode 100644 index 12cd4e3880..0000000000 --- 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 3c47f4c18b..0000000000 --- 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 9a73e44f87..0000000000 --- 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 deleted file mode 100644 index 96e4695784..0000000000 --- a/Content.Server/Light/EntitySystems/MatchstickSystem.cs +++ /dev/null @@ -1,124 +0,0 @@ -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> _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; - - var position = _transformSystem.GetGridOrMapTilePosition(match, xform); - - _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; - - 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) - { - 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); - } - } - } -} diff --git a/Content.Shared/IgnitionSource/Components/MatchboxComponent.cs b/Content.Shared/IgnitionSource/Components/MatchboxComponent.cs new file mode 100644 index 0000000000..dda0ca131f --- /dev/null +++ b/Content.Shared/IgnitionSource/Components/MatchboxComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.IgnitionSource.Components; + +/// +/// Component for entities that light matches when they interact. (E.g. striking the match on the matchbox) +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class MatchboxComponent : Component; + diff --git a/Content.Shared/IgnitionSource/Components/MatchstickComponent.cs b/Content.Shared/IgnitionSource/Components/MatchstickComponent.cs new file mode 100644 index 0000000000..d1bbae42d9 --- /dev/null +++ b/Content.Shared/IgnitionSource/Components/MatchstickComponent.cs @@ -0,0 +1,34 @@ +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 +{ + /// + /// Current state to matchstick. Can be Unlit, Lit or Burnt. + /// + [DataField, AutoNetworkedField] + public SmokableState State = SmokableState.Unlit; + + /// + /// How long the matchstick will burn for. + /// + [DataField, AutoNetworkedField] + public TimeSpan Duration = TimeSpan.FromSeconds(10); + + /// + /// The time that the match will burn out. If null, that means the match is unlit. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan? TimeMatchWillBurnOut; + + /// + /// Sound played when you ignite the matchstick. + /// + [DataField] + public SoundSpecifier? IgniteSound; +} diff --git a/Content.Shared/IgnitionSource/EntitySystems/MatchboxSystem.cs b/Content.Shared/IgnitionSource/EntitySystems/MatchboxSystem.cs new file mode 100644 index 0000000000..806e1e9eb1 --- /dev/null +++ b/Content.Shared/IgnitionSource/EntitySystems/MatchboxSystem.cs @@ -0,0 +1,25 @@ +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(OnInteractUsing, before: [ typeof(SharedStorageSystem) ]); + } + + private void OnInteractUsing(Entity ent, ref InteractUsingEvent args) + { + if (args.Handled || !TryComp(args.Used, out var matchstick)) + return; + + args.Handled = _match.TryIgnite((args.Used, matchstick), args.User); + } +} diff --git a/Content.Shared/IgnitionSource/EntitySystems/MatchstickSystem.cs b/Content.Shared/IgnitionSource/EntitySystems/MatchstickSystem.cs new file mode 100644 index 0000000000..1267af307d --- /dev/null +++ b/Content.Shared/IgnitionSource/EntitySystems/MatchstickSystem.cs @@ -0,0 +1,102 @@ +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(OnInteractUsing); + } + + // This is for something *else* lighting the matchstick, not the matchstick lighting something else. + private void OnInteractUsing(Entity 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); + } + + /// + /// Try to light a matchstick! + /// + /// The matchstick to light. + /// The user lighting the matchstick can be null if there isn't any user. + /// True if the matchstick was lit, false otherwise. + public bool TryIgnite(Entity 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 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(); + + 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); + } + } +} diff --git a/Resources/Prototypes/Entities/Objects/Tools/matches.yml b/Resources/Prototypes/Entities/Objects/Tools/matches.yml index e8601fcf35..ee5100c999 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/matches.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/matches.yml @@ -32,6 +32,12 @@ 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