SolutionContainer refactors (#2954)
* removes unused method * Code uncluttering (Also removed the netcode, color code, and visuals, need to rewrite) * SolutionContainerVisualState * Removes caching of SolutionContainer Color * ChemicalsAdded() and ChemicalsRemoved() for updating appearance and handling reaction checks * SolutionContainerComponentState * Netcode * ChemMasterComponent no longer creates a SolutionContainerComponent with new(), uses a Solution instead * Enable nullable in SolutionContainer implementations * Some review fixes * uses IReadOnlyLists in ChemMaster * Comments * review fixes 3 * ReagentUnit documentation * Review fixes * spelling fix * spelling 2 * typo Co-authored-by: py01 <pyronetics01@gmail.com>
This commit is contained in:
@@ -1,100 +1,230 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
#nullable enable
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Appearance;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
{
|
||||
public abstract class SharedSolutionContainerComponent : Component
|
||||
/// <summary>
|
||||
/// Holds a <see cref="Solution"/> with a limited volume.
|
||||
/// </summary>
|
||||
public abstract class SharedSolutionContainerComponent : Component, IExamine
|
||||
{
|
||||
public override string Name => "SolutionContainer";
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override uint? NetID => ContentNetIDs.SOLUTION;
|
||||
|
||||
private Solution _solution = new();
|
||||
private ReagentUnit _maxVolume;
|
||||
private Color _substanceColor;
|
||||
|
||||
/// <summary>
|
||||
/// The contained solution.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Solution Solution
|
||||
{
|
||||
get => _solution;
|
||||
set
|
||||
{
|
||||
if (_solution == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public Solution Solution { get; private set; } = new();
|
||||
|
||||
_solution = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit MaxVolume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total volume of all the of the reagents in the container.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ReagentUnit CurrentVolume => Solution.TotalVolume;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum volume of the container.
|
||||
/// Volume needed to fill this container.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => _maxVolume;
|
||||
set
|
||||
{
|
||||
if (_maxVolume == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
[ViewVariables]
|
||||
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
|
||||
|
||||
_maxVolume = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
[ViewVariables]
|
||||
public virtual Color Color => Solution.Color;
|
||||
|
||||
/// <summary>
|
||||
/// The current blended color of all the reagents in the container.
|
||||
/// If reactions will be checked for when adding reagents to the container.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public virtual Color SubstanceColor
|
||||
{
|
||||
get => _substanceColor;
|
||||
set
|
||||
{
|
||||
if (_substanceColor == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public bool CanReact { get; set; }
|
||||
|
||||
_substanceColor = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SolutionContainerCaps Capabilities { get; set; }
|
||||
|
||||
public abstract bool CanAddSolution(Solution solution);
|
||||
public bool CanExamineContents => Capabilities.HasCap(SolutionContainerCaps.CanExamine);
|
||||
|
||||
public abstract bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false);
|
||||
public bool CanUseWithChemDispenser => Capabilities.HasCap(SolutionContainerCaps.FitsInDispenser);
|
||||
|
||||
public abstract bool TryRemoveReagent(string reagentId, ReagentUnit quantity);
|
||||
public bool CanAddSolutions => Capabilities.HasCap(SolutionContainerCaps.AddTo);
|
||||
|
||||
public bool CanRemoveSolutions => Capabilities.HasCap(SolutionContainerCaps.RemoveFrom);
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.CanReact, "canReact", true);
|
||||
serializer.DataField(this, x => x.MaxVolume, "maxVol", ReagentUnit.New(0));
|
||||
serializer.DataField(this, x => x.Solution, "contents", new Solution());
|
||||
serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom | SolutionContainerCaps.CanExamine);
|
||||
}
|
||||
|
||||
public void RemoveAllSolution()
|
||||
{
|
||||
if (CurrentVolume == 0)
|
||||
return;
|
||||
|
||||
Solution.RemoveAllSolution();
|
||||
ChemicalsRemoved();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds reagent of an Id to the container.
|
||||
/// </summary>
|
||||
/// <param name="reagentId">The Id of the reagent to add.</param>
|
||||
/// <param name="quantity">The amount of reagent to add.</param>
|
||||
/// <param name="acceptedQuantity">The amount of reagent sucesfully added.</param>
|
||||
/// <returns>If all the reagent could be added.</returns>
|
||||
public bool TryAddReagent(string reagentId, ReagentUnit quantity, out ReagentUnit acceptedQuantity)
|
||||
{
|
||||
acceptedQuantity = EmptyVolume > quantity ? quantity : EmptyVolume;
|
||||
Solution.AddReagent(reagentId, acceptedQuantity);
|
||||
|
||||
if (acceptedQuantity > 0)
|
||||
ChemicalsAdded();
|
||||
|
||||
return acceptedQuantity == quantity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent of an Id to the container.
|
||||
/// </summary>
|
||||
/// <param name="reagentId">The Id of the reagent to remove.</param>
|
||||
/// <param name="quantity">The amount of reagent to remove.</param>
|
||||
/// <returns>If the reagent to remove was found in the container.</returns>
|
||||
public bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
|
||||
{
|
||||
if (!Solution.ContainsReagent(reagentId))
|
||||
return false;
|
||||
|
||||
Solution.RemoveReagent(reagentId, quantity);
|
||||
ChemicalsRemoved();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes part of the solution in the container.
|
||||
/// </summary>
|
||||
/// <param name="quantity">the volume of solution to remove.</param>
|
||||
/// <returns>The solution that was removed.</returns>
|
||||
public Solution SplitSolution(ReagentUnit quantity)
|
||||
{
|
||||
var splitSol = Solution.SplitSolution(quantity);
|
||||
ChemicalsRemoved();
|
||||
return splitSol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a solution can fit into the container.
|
||||
/// </summary>
|
||||
/// <param name="solution">The solution that is trying to be added.</param>
|
||||
/// <returns>If the solution can be fully added.</returns>
|
||||
public bool CanAddSolution(Solution solution)
|
||||
{
|
||||
return solution.TotalVolume <= EmptyVolume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a solution to the container, if it can fully fit.
|
||||
/// </summary>
|
||||
/// <param name="solution">The solution to try to add.</param>
|
||||
/// <returns>If the solution could be added.</returns>
|
||||
public bool TryAddSolution(Solution solution)
|
||||
{
|
||||
if (!CanAddSolution(solution))
|
||||
return false;
|
||||
|
||||
Solution.AddSolution(solution);
|
||||
ChemicalsAdded();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ChemicalsAdded()
|
||||
{
|
||||
ProcessReactions();
|
||||
SolutionChanged();
|
||||
UpdateAppearance();
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void ChemicalsRemoved()
|
||||
{
|
||||
SolutionChanged();
|
||||
UpdateAppearance();
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void SolutionChanged()
|
||||
{
|
||||
EntitySystem.Get<ChemistrySystem>()
|
||||
.HandleSolutionChange(Owner);
|
||||
}
|
||||
|
||||
private void ProcessReactions()
|
||||
{
|
||||
if (!CanReact)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<ChemicalReactionSystem>()
|
||||
.FullyReactSolution(Solution, Owner, MaxVolume);
|
||||
}
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (!CanExamineContents)
|
||||
return;
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
if (ReagentList.Count == 0)
|
||||
{
|
||||
message.AddText(Loc.GetString("Contains no chemicals."));
|
||||
return;
|
||||
}
|
||||
|
||||
var primaryReagent = Solution.GetPrimaryReagentId();
|
||||
if (!prototypeManager.TryIndex(primaryReagent, out ReagentPrototype proto))
|
||||
{
|
||||
Logger.Error($"{nameof(SharedSolutionContainerComponent)} could not find the prototype associated with {primaryReagent}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var colorHex = Color.ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable.
|
||||
var messageString = "It contains a [color={0}]{1}[/color] " + (ReagentList.Count == 1 ? "chemical." : "mixture of chemicals.");
|
||||
|
||||
message.AddMarkup(Loc.GetString(messageString, colorHex, Loc.GetString(proto.PhysicalDescription)));
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (!Owner.TryGetComponent<SharedAppearanceComponent>(out var appearance))
|
||||
return;
|
||||
|
||||
appearance.SetData(SolutionContainerVisuals.VisualState, GetVisualState());
|
||||
}
|
||||
|
||||
private SolutionContainerVisualState GetVisualState()
|
||||
{
|
||||
var filledVolumeFraction = CurrentVolume.Float() / MaxVolume.Float();
|
||||
|
||||
return new SolutionContainerVisualState(Color, filledVolumeFraction);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new SolutionContainerComponentState(Solution);
|
||||
@@ -102,14 +232,34 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not SolutionContainerComponentState state)
|
||||
{
|
||||
if (curState is not SolutionContainerComponentState containerState)
|
||||
return;
|
||||
}
|
||||
|
||||
_solution = state.Solution;
|
||||
Solution = containerState.Solution;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum SolutionContainerVisuals : byte
|
||||
{
|
||||
VisualState
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class SolutionContainerVisualState
|
||||
{
|
||||
public readonly Color Color;
|
||||
|
||||
/// <summary>
|
||||
/// Represents how full the container is, as a fraction equivalent to <see cref="FilledVolumeFraction"/>/<see cref="byte.MaxValue"/>.
|
||||
/// </summary>
|
||||
public readonly byte FilledVolumeFraction;
|
||||
|
||||
/// <param name="filledVolumeFraction">The fraction of the container's volume that is filled.</param>
|
||||
public SolutionContainerVisualState(Color color, float filledVolumeFraction)
|
||||
{
|
||||
Color = color;
|
||||
FilledVolumeFraction = (byte) (byte.MaxValue * filledVolumeFraction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user