Predict ReactionMixerSystem (#41218)

* predict

* documentation

* review
This commit is contained in:
slarticodefast 2025-12-05 21:46:38 +01:00 committed by BarryNorfolk
parent 2692b91189
commit 4f4945d8c6
3 changed files with 122 additions and 99 deletions

View File

@ -1,81 +0,0 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Chemistry.EntitySystems;
using Content.Server.Popups;
namespace Content.Server.Chemistry.EntitySystems;
public sealed partial class ReactionMixerSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReactionMixerComponent, AfterInteractEvent>(OnAfterInteract, before: [typeof(IngestionSystem)]);
SubscribeLocalEvent<ReactionMixerComponent, ShakeEvent>(OnShake);
SubscribeLocalEvent<ReactionMixerComponent, ReactionMixDoAfterEvent>(OnDoAfter);
}
private void OnAfterInteract(Entity<ReactionMixerComponent> entity, ref AfterInteractEvent args)
{
if (!args.Target.HasValue || !args.CanReach || !entity.Comp.MixOnInteract)
return;
if (!MixAttempt(entity, args.Target.Value, out _))
return;
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.TimeToMix, new ReactionMixDoAfterEvent(), entity, args.Target.Value, entity);
_doAfterSystem.TryStartDoAfter(doAfterArgs);
args.Handled = true;
}
private void OnDoAfter(Entity<ReactionMixerComponent> entity, ref ReactionMixDoAfterEvent args)
{
//Do again to get the solution again
if (!MixAttempt(entity, args.Target!.Value, out var solution))
return;
_popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target!.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User);
_solutionContainers.UpdateChemicals(solution!.Value, true, entity.Comp);
var afterMixingEvent = new AfterMixingEvent(entity, args.Target!.Value);
RaiseLocalEvent(entity, afterMixingEvent);
}
private void OnShake(Entity<ReactionMixerComponent> entity, ref ShakeEvent args)
{
if (!MixAttempt(entity, entity, out var solution))
return;
_solutionContainers.UpdateChemicals(solution!.Value, true, entity.Comp);
var afterMixingEvent = new AfterMixingEvent(entity, entity);
RaiseLocalEvent(entity, afterMixingEvent);
}
private bool MixAttempt(EntityUid ent, EntityUid target, out Entity<SolutionComponent>? solution)
{
solution = null;
var mixAttemptEvent = new MixingAttemptEvent(ent);
RaiseLocalEvent(ent, ref mixAttemptEvent);
if (mixAttemptEvent.Cancelled)
{
return false;
}
if (!_solutionContainers.TryGetMixableSolution(target, out solution, out _))
return false;
return true;
}
}

View File

@ -1,48 +1,43 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry.Reaction;
[RegisterComponent]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ReactionMixerComponent : Component
{
/// <summary>
/// A list of IDs for categories of reactions that can be mixed (i.e. HOLY for a bible, DRINK for a spoon)
/// A list of IDs for categories of reactions that can be mixed (i.e. HOLY for a bible, DRINK for a spoon).
/// </summary>
[ViewVariables]
[DataField]
public List<ProtoId<MixingCategoryPrototype>> ReactionTypes = default!;
[DataField, AutoNetworkedField]
public List<ProtoId<MixingCategoryPrototype>> ReactionTypes = new();
/// <summary>
/// A string which identifies the string to be sent when successfully mixing a solution
/// The popup message when successfully mixing a solution.
/// </summary>
[ViewVariables]
[DataField]
[DataField, AutoNetworkedField]
public LocId MixMessage = "default-mixing-success";
/// <summary>
/// Defines if interacting is enough to mix with this component
/// Defines if interacting is enough to mix with this component.
/// </summary>
[ViewVariables]
[DataField]
[DataField, AutoNetworkedField]
public bool MixOnInteract = true;
/// <summary>
/// How long it takes to mix with this
/// How long it takes to mix with this.
/// </summary>
[ViewVariables]
[DataField]
[DataField, AutoNetworkedField]
public TimeSpan TimeToMix = TimeSpan.Zero;
}
[ByRefEvent]
public record struct MixingAttemptEvent(EntityUid Mixed, bool Cancelled = false);
[ByRefEvent]
public readonly record struct AfterMixingEvent(EntityUid Mixed, EntityUid Mixer);
[Serializable, NetSerializable]
public sealed partial class ReactionMixDoAfterEvent : SimpleDoAfterEvent
{
}
public sealed partial class ReactionMixDoAfterEvent : SimpleDoAfterEvent;

View File

@ -0,0 +1,109 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Popups;
namespace Content.Shared.Chemistry.Reaction;
public sealed partial class ReactionMixerSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReactionMixerComponent, AfterInteractEvent>(OnAfterInteract, before: [typeof(IngestionSystem)]);
SubscribeLocalEvent<ReactionMixerComponent, ShakeEvent>(OnShake);
SubscribeLocalEvent<ReactionMixerComponent, ReactionMixDoAfterEvent>(OnDoAfter);
}
private void OnAfterInteract(Entity<ReactionMixerComponent> ent, ref AfterInteractEvent args)
{
if (!args.Target.HasValue || !args.CanReach || !ent.Comp.MixOnInteract)
return;
if (!CanMix(ent.AsNullable(), args.Target.Value))
return;
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, ent.Comp.TimeToMix, new ReactionMixDoAfterEvent(), ent, args.Target.Value, ent);
_doAfter.TryStartDoAfter(doAfterArgs);
args.Handled = true;
}
private void OnDoAfter(Entity<ReactionMixerComponent> ent, ref ReactionMixDoAfterEvent args)
{
if (args.Target == null)
return;
if (!TryMix(ent.AsNullable(), args.Target.Value))
return;
_popup.PopupClient(
Loc.GetString(
ent.Comp.MixMessage,
("mixed", Identity.Entity(args.Target.Value, EntityManager)),
("mixer", Identity.Entity(ent.Owner, EntityManager))),
args.User,
args.User);
}
private void OnShake(Entity<ReactionMixerComponent> ent, ref ShakeEvent args)
{
TryMix(ent.AsNullable(), ent);
}
/// <summary>
/// Returns true if given reaction mixer is able to mix the solution inside the target entity, false otherwise.
/// </summary>
/// <param name="ent">The reaction mixer used to cause the reaction.</param>
/// <param name="target">The target solution container with a <see cref="MixableSolutionComponent"/>.</param>
public bool CanMix(Entity<ReactionMixerComponent?> ent, EntityUid target)
{
if (!Resolve(ent, ref ent.Comp, false)) // The used entity needs the component to be able to mix a solution
return false;
var mixAttemptEvent = new MixingAttemptEvent(ent);
RaiseLocalEvent(ent, ref mixAttemptEvent);
if (mixAttemptEvent.Cancelled)
return false;
if (!_solutionContainer.TryGetMixableSolution(target, out _, out _))
return false;
return true;
}
/// <summary>
/// Attempts to mix the solution inside the target entity using the given reaction mixer.
/// </summary>
/// <param name="ent">The reaction mixer used to cause the reaction.</param>
/// <param name="target">The target solution container with a <see cref="MixableSolutionComponent"/>.</param>
/// <returns>If the reaction mixer was able to mix the solution. This does not necessarily mean a reaction took place.</returns>
public bool TryMix(Entity<ReactionMixerComponent?> ent, EntityUid target)
{
if (!Resolve(ent, ref ent.Comp, false))
return false;
var mixAttemptEvent = new MixingAttemptEvent(ent);
RaiseLocalEvent(ent, ref mixAttemptEvent);
if (mixAttemptEvent.Cancelled)
return false;
if (!_solutionContainer.TryGetMixableSolution(target, out var solutionEnt, out _))
return false;
_solutionContainer.UpdateChemicals(solutionEnt.Value, true, ent.Comp);
var afterMixingEvent = new AfterMixingEvent(ent, target);
RaiseLocalEvent(ent, ref afterMixingEvent);
return true;
}
}