Chem guidebook (#17123)

* im good at atomizing. welcome to half-finished chem guides.

* wagh

* e

* save work

* aa

* woweee UI

* finishing the last of it

* don't actually update the engine :(

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
This commit is contained in:
Nemanja
2023-06-04 16:45:02 -04:00
committed by GitHub
parent 1e6dbd0b67
commit b9fb66f005
72 changed files with 1411 additions and 12 deletions

View File

@@ -0,0 +1,29 @@
using Content.Shared.Chemistry;
namespace Content.Client.Chemistry.EntitySystems;
/// <inheritdoc/>
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
{
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<ReagentGuideRegistryChangedEvent>(OnReceiveRegistryUpdate);
}
private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message)
{
var data = message.Changeset;
foreach (var remove in data.Removed)
{
Registry.Remove(remove);
}
foreach (var (key, val) in data.GuideEntries)
{
Registry[key] = val;
}
}
}

View File

@@ -0,0 +1,58 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Orientation="Vertical"
Margin="5 5 5 5">
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="#777777"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<PanelContainer Name="NameBackground" HorizontalExpand="True" VerticalExpand="False">
<RichTextLabel Name="ReagentName" HorizontalAlignment="Center"/>
</PanelContainer>
<BoxContainer Name="RecipesContainer" HorizontalExpand="True">
<Collapsible Orientation="Vertical" HorizontalExpand="True">
<CollapsibleHeading Title="{Loc 'guidebook-reagent-recipes-header'}"/>
<CollapsibleBody>
<BoxContainer Name="RecipesDescriptionContainer"
Margin="10 0 10 0"
Orientation="Horizontal"
HorizontalAlignment="Stretch"
HorizontalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
<RichTextLabel Name="ReactantsLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
<TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png"/>
<Label Text="{Loc 'guidebook-reagent-recipes-mix'}"
HorizontalAlignment="Center"/>
</BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
<RichTextLabel Name="ProductsLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</BoxContainer>
</BoxContainer>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
<BoxContainer Name="EffectsContainer" HorizontalExpand="True">
<Collapsible Orientation="Vertical">
<CollapsibleHeading Title="{Loc 'guidebook-reagent-effects-header'}"/>
<CollapsibleBody>
<BoxContainer Name="EffectsDescriptionContainer"
Orientation="Vertical"
Margin="10 0 10 0"
HorizontalExpand="True"/>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
<BoxContainer Margin="10 5 10 10" HorizontalExpand="True">
<!-- The troublemaker !-->
<RichTextLabel Name="ReagentDescription" HorizontalAlignment="Left"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -0,0 +1,176 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Chemistry.EntitySystems;
using Content.Client.Guidebook.Richtext;
using Content.Client.Message;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Guidebook.Controls;
/// <summary>
/// Control for embedding a reagent into a guidebook.
/// </summary>
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ChemistryGuideDataSystem _chemistryGuideData;
public GuideReagentEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_chemistryGuideData = _systemManager.GetEntitySystem<ChemistryGuideDataSystem>();
MouseFilter = MouseFilterMode.Stop;
}
public GuideReagentEmbed(string reagent) : this()
{
GenerateControl(_prototype.Index<ReagentPrototype>(reagent));
}
public GuideReagentEmbed(ReagentPrototype reagent) : this()
{
GenerateControl(reagent);
}
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = null;
if (!args.TryGetValue("Reagent", out var id))
{
Logger.Error("Reagent embed tag is missing reagent prototype argument");
return false;
}
if (!_prototype.TryIndex<ReagentPrototype>(id, out var reagent))
{
Logger.Error($"Specified reagent prototype \"{id}\" is not a valid reagent prototype");
return false;
}
GenerateControl(reagent);
control = this;
return true;
}
private void GenerateControl(ReagentPrototype reagent)
{
NameBackground.PanelOverride = new StyleBoxFlat
{
BackgroundColor = reagent.SubstanceColor
};
var textColor = Color.ToHsl(reagent.SubstanceColor).Z > 0.45
? Color.Black
: Color.White;
ReagentName.SetMarkup(Loc.GetString("guidebook-reagent-name",
("color", textColor), ("name", reagent.LocalizedName)));
#region Recipe
// by default, we assume that the reaction has the same ID as the reagent.
// if this isn't true, we'll loop through reactions.
if (!_prototype.TryIndex<ReactionPrototype>(reagent.ID, out var reactionPrototype))
{
reactionPrototype = _prototype.EnumeratePrototypes<ReactionPrototype>()
.FirstOrDefault(p => p.Products.ContainsKey(reagent.ID));
}
if (reactionPrototype != null)
{
var reactantMsg = new FormattedMessage();
var reactantsCount = reactionPrototype.Reactants.Count;
var i = 0;
foreach (var (product, reactant) in reactionPrototype.Reactants)
{
reactantMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
("reagent", _prototype.Index<ReagentPrototype>(product).LocalizedName), ("ratio", reactant.Amount)));
i++;
if (i < reactantsCount)
reactantMsg.PushNewline();
}
reactantMsg.Pop();
ReactantsLabel.SetMessage(reactantMsg);
var productMsg = new FormattedMessage();
var productCount = reactionPrototype.Products.Count;
var u = 0;
foreach (var (product, ratio) in reactionPrototype.Products)
{
productMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
("reagent", _prototype.Index<ReagentPrototype>(product).LocalizedName), ("ratio", ratio)));
u++;
if (u < productCount)
productMsg.PushNewline();
}
productMsg.Pop();
ProductsLabel.SetMessage(productMsg);
}
else
{
RecipesContainer.Visible = false;
}
#endregion
#region Effects
if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistry) &&
guideEntryRegistry.GuideEntries != null &&
guideEntryRegistry.GuideEntries.Values.Any(pair => pair.EffectDescriptions.Any()))
{
EffectsDescriptionContainer.Children.Clear();
foreach (var (group, effect) in guideEntryRegistry.GuideEntries)
{
if (!effect.EffectDescriptions.Any())
continue;
var groupLabel = new RichTextLabel();
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
("group", group), ("rate", effect.MetabolismRate)));
var descriptionLabel = new RichTextLabel
{
Margin = new Thickness(25, 0, 10, 0)
};
var descMsg = new FormattedMessage();
var descriptionsCount = effect.EffectDescriptions.Length;
var i = 0;
foreach (var effectString in effect.EffectDescriptions)
{
descMsg.AddMarkup(effectString);
i++;
if (i < descriptionsCount)
descMsg.PushNewline();
}
descriptionLabel.SetMessage(descMsg);
EffectsDescriptionContainer.AddChild(groupLabel);
EffectsDescriptionContainer.AddChild(descriptionLabel);
}
}
else
{
EffectsContainer.Visible = false;
}
#endregion
FormattedMessage description = new();
description.AddText(reagent.LocalizedDescription);
description.PushNewline();
description.AddText(Loc.GetString("guidebook-reagent-physical-description",
("description", reagent.LocalizedPhysicalDescription)));
ReagentDescription.SetMessage(description);
}
}

View File

@@ -0,0 +1,4 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical">
<BoxContainer Name="GroupContainer" Orientation="Vertical"/>
</BoxContainer>

View File

@@ -0,0 +1,60 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
/// <summary>
/// Control for embedding a reagent into a guidebook.
/// </summary>
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideReagentGroupEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public GuideReagentGroupEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MouseFilter = MouseFilterMode.Stop;
}
public GuideReagentGroupEmbed(string group) : this()
{
var prototypes = _prototype.EnumeratePrototypes<ReagentPrototype>()
.Where(p => p.Group.Equals(group)).OrderBy(p => p.LocalizedName);
foreach (var reagent in prototypes)
{
var embed = new GuideReagentEmbed(reagent);
GroupContainer.AddChild(embed);
}
}
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = null;
if (!args.TryGetValue("Group", out var group))
{
Logger.Error("Reagent group embed tag is missing group argument");
return false;
}
var prototypes = _prototype.EnumeratePrototypes<ReagentPrototype>()
.Where(p => p.Group.Equals(group)).OrderBy(p => p.LocalizedName);
foreach (var reagent in prototypes)
{
var embed = new GuideReagentEmbed(reagent);
GroupContainer.AddChild(embed);
}
control = this;
return true;
}
}

View File

@@ -0,0 +1,47 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.Commands;
[AdminCommand(AdminFlags.Debug)]
public sealed class DumpReagentGuideText : IConsoleCommand
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IEntitySystemManager _entSys = default!;
public string Command => "dumpreagentguidetext";
public string Description => "Dumps the guidebook text for a reagent to the console";
public string Help => "dumpreagentguidetext <reagent>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError("Must have only 1 argument");
return;
}
if (!_prototype.TryIndex<ReagentPrototype>(args[0], out var reagent))
{
shell.WriteError($"Invalid prototype: {args[0]}");
return;
}
if (reagent.Metabolisms is null)
{
shell.WriteLine("Nothing to dump.");
return;
}
foreach (var (_, entry) in reagent.Metabolisms)
{
foreach (var effect in entry.Effects)
{
shell.WriteLine(effect.GuidebookEffectDescription(_prototype, _entSys) ?? $"[skipped effect of type {effect.GetType()}]");
}
}
}
}

View File

@@ -0,0 +1,67 @@
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.EntitySystems;
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
PrototypeManager.PrototypesReloaded += PrototypeManagerReload;
_player.PlayerStatusChanged += OnPlayerStatusChanged;
InitializeServerRegistry();
}
private void InitializeServerRegistry()
{
var changeset = new ReagentGuideChangeset(new Dictionary<string, ReagentGuideEntry>(), new HashSet<string>());
foreach (var proto in PrototypeManager.EnumeratePrototypes<ReagentPrototype>())
{
var entry = new ReagentGuideEntry(proto, PrototypeManager, EntityManager.EntitySysManager);
changeset.GuideEntries.Add(proto.ID, entry);
Registry[proto.ID] = entry;
}
var ev = new ReagentGuideRegistryChangedEvent(changeset);
RaiseNetworkEvent(ev);
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Connected)
return;
var sendEv = new ReagentGuideRegistryChangedEvent(new ReagentGuideChangeset(Registry, new HashSet<string>()));
RaiseNetworkEvent(sendEv, e.Session);
}
private void PrototypeManagerReload(PrototypesReloadedEventArgs obj)
{
if (!obj.ByType.TryGetValue(typeof(ReagentPrototype), out var reagents))
return;
var changeset = new ReagentGuideChangeset(new Dictionary<string, ReagentGuideEntry>(), new HashSet<string>());
foreach (var (id, proto) in reagents.Modified)
{
var reagentProto = (ReagentPrototype) proto;
var entry = new ReagentGuideEntry(reagentProto, PrototypeManager, EntityManager.EntitySysManager);
changeset.GuideEntries.Add(id, entry);
Registry[id] = entry;
}
var ev = new ReagentGuideRegistryChangedEvent(changeset);
RaiseNetworkEvent(ev);
}
}

View File

@@ -45,6 +45,10 @@ namespace Content.Server.Chemistry.ReactionEffects
[DataField("sound", required: true)] private SoundSpecifier _sound = default!; [DataField("sound", required: true)] private SoundSpecifier _sound = default!;
public override bool ShouldLog => true; public override bool ShouldLog => true;
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-missing");
public override LogImpact LogImpact => LogImpact.High; public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)

View File

@@ -19,6 +19,12 @@ public sealed class CreateEntityReactionEffect : ReagentEffect
[DataField("number")] [DataField("number")]
public uint Number = 1; public uint Number = 1;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-create-entity-reaction-effect",
("chance", Probability),
("entname", IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
("amount", Number));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var transform = args.EntityManager.GetComponent<TransformComponent>(args.SolutionEntity); var transform = args.EntityManager.GetComponent<TransformComponent>(args.SolutionEntity);

View File

@@ -3,6 +3,7 @@ using Content.Server.Explosion.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Explosion; using Content.Shared.Explosion;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReactionEffects namespace Content.Server.Chemistry.ReactionEffects
@@ -51,6 +52,9 @@ namespace Content.Server.Chemistry.ReactionEffects
public float IntensityPerUnit = 1; public float IntensityPerUnit = 1;
public override bool ShouldLog => true; public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.High; public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)

View File

@@ -0,0 +1 @@


View File

@@ -16,6 +16,10 @@ namespace Content.Server.Chemistry.ReactionEffects
/// </summary> /// </summary>
[DataField("temperature", required: true)] private float _temperature; [DataField("temperature", required: true)] private float _temperature;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-set-solution-temperature-effect",
("chance", Probability), ("temperature", _temperature));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var solution = args.Source; var solution = args.Source;
@@ -52,6 +56,10 @@ namespace Content.Server.Chemistry.ReactionEffects
/// </summary> /// </summary>
[DataField("scaled")] private bool _scaled; [DataField("scaled")] private bool _scaled;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect",
("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var solution = args.Source; var solution = args.Source;
@@ -106,6 +114,10 @@ namespace Content.Server.Chemistry.ReactionEffects
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp); solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect",
("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp));
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Server.Temperature.Components; using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions namespace Content.Server.Chemistry.ReagentEffectConditions
{ {
@@ -13,7 +14,7 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
public float Min = 0; public float Min = 0;
[DataField("max")] [DataField("max")]
public float Max = float.MaxValue; public float Max = float.PositiveInfinity;
public override bool Condition(ReagentEffectArgs args) public override bool Condition(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp)) if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp))
@@ -24,5 +25,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return false; return false;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-body-temperature",
("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
("min", Min));
}
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Tag; using Content.Shared.Tag;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffectConditions; namespace Content.Server.Chemistry.ReagentEffectConditions;
@@ -21,4 +22,10 @@ public sealed class HasTag : ReagentEffectCondition
return false; return false;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
// this should somehow be made (much) nicer.
return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Invert));
}
} }

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions namespace Content.Server.Chemistry.ReagentEffectConditions
{ {
@@ -21,6 +22,11 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return false; return false;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", mobstate));
}
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Shared.Body.Prototypes; using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffectConditions namespace Content.Server.Chemistry.ReagentEffectConditions
@@ -30,5 +31,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return ShouldHave; return ShouldHave;
return !ShouldHave; return !ShouldHave;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
("name", prototype.Index<MetabolizerTypePrototype>(Type).Name),
("shouldhave", ShouldHave));
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions namespace Content.Server.Chemistry.ReagentEffectConditions
{ {
@@ -35,5 +36,17 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return quant >= Min && quant <= Max; return quant >= Min && quant <= Max;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
ReagentPrototype? reagentProto = null;
if (Reagent is not null)
prototype.TryIndex(Reagent, out reagentProto);
return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
("reagent", reagentProto?.LocalizedName ?? "this reagent"),
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
} }
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions namespace Content.Server.Chemistry.ReagentEffectConditions
{ {
@@ -24,5 +25,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return true; return true;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-solution-temperature",
("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
("min", Min));
}
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions namespace Content.Server.Chemistry.ReagentEffectConditions
{ {
@@ -23,5 +24,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return false; return false;
} }
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-total-damage",
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Server.Xenoarchaeology.XenoArtifacts;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -10,4 +11,7 @@ public sealed class ActivateArtifact : ReagentEffect
var artifact = args.EntityManager.EntitySysManager.GetEntitySystem<ArtifactSystem>(); var artifact = args.EntityManager.EntitySysManager.GetEntitySystem<ArtifactSystem>();
artifact.TryActivateArtifact(args.SolutionEntity); artifact.TryActivateArtifact(args.SolutionEntity);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-activate-artifact", ("chance", Probability));
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -24,5 +25,8 @@ namespace Content.Server.Chemistry.ReagentEffects
.TryAddReagent(args.SolutionEntity, solutionContainer, args.Reagent.ID, args.Quantity, out var accepted)) .TryAddReagent(args.SolutionEntity, solutionContainer, args.Reagent.ID, args.Quantity, out var accepted))
args.Source?.RemoveReagent(args.Reagent.ID, accepted); args.Source?.RemoveReagent(args.Reagent.ID, accepted);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -18,6 +19,9 @@ public sealed class AdjustAlert : ReagentEffect
[DataField("time")] [DataField("time")]
public float Time; public float Time;
//JUSTIFICATION: This just changes some visuals, doesn't need to be documented.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var alertSys = EntitySystem.Get<AlertsSystem>(); var alertSys = EntitySystem.Get<AlertsSystem>();

View File

@@ -60,5 +60,27 @@ namespace Content.Server.Chemistry.ReagentEffects
} }
} }
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (Reagent is not null && prototype.TryIndex(Reagent, out ReagentPrototype? reagentProto))
{
return Loc.GetString("reagent-effect-guidebook-adjust-reagent-reagent",
("chance", Probability),
("deltasign", MathF.Sign(Amount.Float())),
("reagent", reagentProto.LocalizedName),
("amount", MathF.Abs(Amount.Float())));
}
else if (Group is not null && prototype.TryIndex(Group, out MetabolismGroupPrototype? groupProto))
{
return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group",
("chance", Probability),
("deltasign", MathF.Sign(Amount.Float())),
("group", groupProto.ID),
("amount", MathF.Abs(Amount.Float())));
}
throw new NotImplementedException();
}
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Temperature.Components; using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems; using Content.Server.Temperature.Systems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -9,6 +10,12 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("amount")] [DataField("amount")]
public float Amount; public float Amount;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-adjust-temperature",
("chance", Probability),
("deltasign", MathF.Sign(Amount)),
("amount", MathF.Abs(Amount)));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp)) if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp))

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReactionEffects namespace Content.Server.Chemistry.ReactionEffects
{ {
@@ -12,6 +13,10 @@ namespace Content.Server.Chemistry.ReactionEffects
{ {
[DataField("cleanseRate")] [DataField("cleanseRate")]
public float CleanseRate = 3.0f; public float CleanseRate = 3.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.Source == null || args.Reagent == null) if (args.Source == null || args.Reagent == null)

View File

@@ -2,6 +2,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Eye.Blinding; using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Systems; using Content.Shared.Eye.Blinding.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -17,6 +18,9 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("amount")] [DataField("amount")]
public int Amount = -1; public int Amount = -1;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-cure-eye-damage", ("chance", Probability), ("deltasign", MathF.Sign(Amount)));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.Scale != 1f) // huh? if (args.Scale != 1f) // huh?

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Server.Medical; using Content.Server.Medical;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -17,6 +18,9 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("hungerAmount")] [DataField("hungerAmount")]
public float HungerAmount = -40f; public float HungerAmount = -40f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.Scale != 1f) if (args.Scale != 1f)

View File

@@ -2,6 +2,7 @@
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -17,6 +18,17 @@ public sealed class CreateGas : ReagentEffect
public float Multiplier = 3f; public float Multiplier = 3f;
public override bool ShouldLog => true; public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var atmos = entSys.GetEntitySystem<AtmosphereSystem>();
var gasProto = atmos.GetGas(Gas);
return Loc.GetString("reagent-effect-guidebook-create-gas",
("chance", Probability),
("moles", Multiplier),
("gas", gasProto.Name));
}
public override LogImpact LogImpact => LogImpact.High; public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)

View File

@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Drunk; using Content.Shared.Drunk;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -17,6 +18,9 @@ public sealed class Drunk : ReagentEffect
[DataField("slurSpeech")] [DataField("slurSpeech")]
public bool SlurSpeech = true; public bool SlurSpeech = true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-drunk", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var boozePower = BoozePower; var boozePower = BoozePower;

View File

@@ -1,5 +1,6 @@
using Content.Server.Electrocution; using Content.Server.Electrocution;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -14,6 +15,9 @@ public sealed class Electrocute : ReagentEffect
/// </remarks> /// </remarks>
[DataField("refresh")] public bool Refresh = true; [DataField("refresh")] public bool Refresh = true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime));
public override bool ShouldLog => true; public override bool ShouldLog => true;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)

View File

@@ -2,6 +2,7 @@ using Content.Server.Chat.Systems;
using Content.Shared.Chat.Prototypes; using Content.Shared.Chat.Prototypes;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -18,6 +19,10 @@ public sealed class Emote : ReagentEffect
[DataField("showInChat")] [DataField("showInChat")]
public bool ShowInChat; public bool ShowInChat;
// JUSTIFICATION: Emoting is flavor, so same reason popup messages are not in here.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (EmoteId == null) if (EmoteId == null)

View File

@@ -2,12 +2,16 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
[UsedImplicitly] [UsedImplicitly]
public sealed class ExtinguishReaction : ReagentEffect public sealed class ExtinguishReaction : ReagentEffect
{ {
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-extinguish-reaction", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable)) return; if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable)) return;

View File

@@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -13,6 +14,10 @@ namespace Content.Server.Chemistry.ReagentEffects
public float Multiplier = 0.05f; public float Multiplier = 0.05f;
public override bool ShouldLog => true; public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-flammable-reaction", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.Medium; public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)

View File

@@ -1,8 +1,11 @@
using System.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Localizations;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -31,6 +34,38 @@ namespace Content.Server.Chemistry.ReagentEffects
[JsonPropertyName("ignoreResistances")] [JsonPropertyName("ignoreResistances")]
public bool IgnoreResistances = true; public bool IgnoreResistances = true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var damages = new List<string>();
var heals = false;
var deals = false;
// TODO: This should be smarter. Namely, not showing a damage type as being in a group unless every damage type in the group is present and equal in value.
foreach (var (kind, amount) in Damage.GetDamagePerGroup())
{
var sign = MathF.Sign(amount.Float());
if (sign < 0)
heals = true;
if (sign > 0)
deals = true;
damages.Add(
Loc.GetString("health-change-display",
("kind", kind),
("amount", MathF.Abs(amount.Float())),
("deltasign", sign)
));
}
var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none");
return Loc.GetString("reagent-effect-guidebook-health-change",
("chance", Probability),
("changes", ContentLocalizationManager.FormatList(damages)),
("healsordeals", healsordeals));
}
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var scale = ScaleByQuantity ? args.Quantity : FixedPoint2.New(1); var scale = ScaleByQuantity ? args.Quantity : FixedPoint2.New(1);

View File

@@ -1,6 +1,7 @@
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -10,6 +11,10 @@ namespace Content.Server.Chemistry.ReagentEffects;
public sealed class Ignite : ReagentEffect public sealed class Ignite : ReagentEffect
{ {
public override bool ShouldLog => true; public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-ignite", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.Medium; public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)

View File

@@ -2,11 +2,16 @@ using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Components; using Content.Server.Mind.Components;
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Server.Ghost.Roles.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
public sealed class MakeSentient : ReagentEffect public sealed class MakeSentient : ReagentEffect
{ {
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var entityManager = args.EntityManager; var entityManager = args.EntityManager;

View File

@@ -1,6 +1,7 @@
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -12,6 +13,10 @@ public sealed class ModifyBleedAmount : ReagentEffect
[DataField("amount")] [DataField("amount")]
public float Amount = -1.0f; public float Amount = -1.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability),
("deltasign", MathF.Sign(Amount)));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.SolutionEntity, out var blood)) if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.SolutionEntity, out var blood))

View File

@@ -2,6 +2,7 @@
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -13,6 +14,10 @@ public sealed class ModifyBloodLevel : ReagentEffect
[DataField("amount")] [DataField("amount")]
public FixedPoint2 Amount = 1.0f; public FixedPoint2 Amount = 1.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability),
("deltasign", MathF.Sign(Amount.Float())));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.SolutionEntity, out var blood)) if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.SolutionEntity, out var blood))

View File

@@ -1,6 +1,7 @@
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -9,6 +10,10 @@ public sealed class ModifyLungGas : ReagentEffect
[DataField("ratios", required: true)] [DataField("ratios", required: true)]
private Dictionary<Gas, float> _ratios = default!; private Dictionary<Gas, float> _ratios = default!;
// JUSTIFICATION: This is internal magic that players never directly interact with.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung)) if (args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
@@ -29,6 +30,14 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("statusLifetime")] [DataField("statusLifetime")]
public float StatusLifetime = 2f; public float StatusLifetime = 2f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("reagent-effect-guidebook-movespeed-modifier",
("chance", Probability),
("walkspeed", WalkSpeedModifier),
("time", StatusLifetime));
}
/// <summary> /// <summary>
/// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there. /// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there.
/// </summary> /// </summary>

View File

@@ -1,6 +1,7 @@
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -9,6 +10,10 @@ public sealed class Oxygenate : ReagentEffect
[DataField("factor")] [DataField("factor")]
public float Factor = 1f; public float Factor = 1f;
// JUSTIFICATION: This is internal magic that players never directly interact with.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.EntityManager.TryGetComponent<RespiratorComponent>(args.SolutionEntity, out var resp)) if (args.EntityManager.TryGetComponent<RespiratorComponent>(args.SolutionEntity, out var resp))

View File

@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Server.Stunnable; using Content.Server.Stunnable;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -12,6 +13,11 @@ public sealed class Paralyze : ReagentEffect
/// </remarks> /// </remarks>
[DataField("refresh")] public bool Refresh = true; [DataField("refresh")] public bool Refresh = true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-paralyze",
("chance", Probability),
("time", ParalyzeTime));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var paralyzeTime = ParalyzeTime; var paralyzeTime = ParalyzeTime;

View File

@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Server.Botany.Components; using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -35,5 +36,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
// Dependencies are never injected for reagents if you intend to do that for this. // Dependencies are never injected for reagents if you intend to do that for this.
return !(Prob <= 0f) && IoCManager.Resolve<IRobustRandom>().Prob(Prob); return !(Prob <= 0f) && IoCManager.Resolve<IRobustRandom>().Prob(Prob);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Botany.Components; using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -26,5 +27,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
plantHolderComp.SkipAging++; plantHolderComp.SkipAging++;
plantHolderComp.ForceUpdate = true; plantHolderComp.ForceUpdate = true;
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
} }
} }

View File

@@ -2,6 +2,7 @@ using Content.Server.Botany.Components;
using Content.Server.Botany.Systems; using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -34,5 +35,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
plantHolderComp.Seed.Endurance++; plantHolderComp.Seed.Endurance++;
} }
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
} }
} }

View File

@@ -2,6 +2,7 @@ using Content.Server.Botany.Components;
using Content.Server.Botany.Systems; using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -47,5 +48,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
plantHolderComp.Seed.Yield--; plantHolderComp.Seed.Yield--;
} }
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Popups; using Content.Shared.Popups;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
@@ -16,6 +17,10 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("visualType")] [DataField("visualType")]
public PopupType VisualType = PopupType.Small; public PopupType VisualType = PopupType.Small;
// JUSTIFICATION: This is purely cosmetic.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
var popupSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedPopupSystem>(); var popupSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedPopupSystem>();

View File

@@ -1,6 +1,7 @@
using Content.Server.Traits.Assorted; using Content.Server.Traits.Assorted;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;
@@ -16,6 +17,9 @@ public sealed class ResetNarcolepsy : ReagentEffect
[DataField("TimerReset")] [DataField("TimerReset")]
public int TimerReset = 600; public int TimerReset = 600;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-reset-narcolepsy", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.Scale != 1f) if (args.Scale != 1f)

View File

@@ -2,6 +2,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -11,10 +12,12 @@ namespace Content.Server.Chemistry.ReagentEffects
/// </summary> /// </summary>
public sealed class SatiateHunger : ReagentEffect public sealed class SatiateHunger : ReagentEffect
{ {
private const float DefaultNutritionFactor = 3.0f;
/// <summary> /// <summary>
/// How much hunger is satiated when 1u of the reagent is metabolized /// How much hunger is satiated when 1u of the reagent is metabolized
/// </summary> /// </summary>
[DataField("factor")] public float NutritionFactor { get; set; } = 3.0f; [DataField("factor")] public float NutritionFactor { get; set; } = DefaultNutritionFactor;
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found //Remove reagent at set rate, satiate hunger if a HungerComponent can be found
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
@@ -24,5 +27,8 @@ namespace Content.Server.Chemistry.ReagentEffects
return; return;
entman.System<HungerSystem>().ModifyHunger(args.SolutionEntity, NutritionFactor * (float) args.Quantity, hunger); entman.System<HungerSystem>().ModifyHunger(args.SolutionEntity, NutritionFactor * (float) args.Quantity, hunger);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", NutritionFactor / DefaultNutritionFactor));
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
@@ -10,10 +11,12 @@ namespace Content.Server.Chemistry.ReagentEffects
/// </summary> /// </summary>
public sealed class SatiateThirst : ReagentEffect public sealed class SatiateThirst : ReagentEffect
{ {
private const float DefaultHydrationFactor = 3.0f;
/// How much thirst is satiated each metabolism tick. Not currently tied to /// How much thirst is satiated each metabolism tick. Not currently tied to
/// rate or anything. /// rate or anything.
[DataField("factor")] [DataField("factor")]
public float HydrationFactor { get; set; } = 3.0f; public float HydrationFactor { get; set; } = DefaultHydrationFactor;
/// Satiate thirst if a ThirstComponent can be found /// Satiate thirst if a ThirstComponent can be found
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
@@ -21,5 +24,8 @@ namespace Content.Server.Chemistry.ReagentEffects
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out ThirstComponent? thirst)) if (args.EntityManager.TryGetComponent(args.SolutionEntity, out ThirstComponent? thirst))
EntitySystem.Get<ThirstSystem>().UpdateThirst(thirst, HydrationFactor); EntitySystem.Get<ThirstSystem>().UpdateThirst(thirst, HydrationFactor);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", HydrationFactor / DefaultHydrationFactor));
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.StatusEffect; using Content.Shared.StatusEffect;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.StatusEffects namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
{ {
@@ -57,6 +58,13 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
statusSys.TrySetTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time)); statusSys.TrySetTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
} }
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString(
"reagent-effect-guidebook-status-effect",
("chance", Probability),
("type", Type),
("time", Time),
("key", $"reagent-effect-status-effect-{Key}"));
} }
public enum StatusEffectMetabolismType public enum StatusEffectMetabolismType

View File

@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Jittering; using Content.Shared.Jittering;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.StatusEffects namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
{ {
@@ -33,5 +34,8 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
args.EntityManager.EntitySysManager.GetEntitySystem<SharedJitteringSystem>() args.EntityManager.EntitySysManager.GetEntitySystem<SharedJitteringSystem>()
.DoJitter(args.SolutionEntity, TimeSpan.FromSeconds(time), Refresh, Amplitude, Frequency); .DoJitter(args.SolutionEntity, TimeSpan.FromSeconds(time), Refresh, Amplitude, Frequency);
} }
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-jittering", ("chance", Probability));
} }
} }

View File

@@ -2,12 +2,16 @@ using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {
[UsedImplicitly] [UsedImplicitly]
public sealed class WashCreamPieReaction : ReagentEffect public sealed class WashCreamPieReaction : ReagentEffect
{ {
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out CreamPiedComponent? creamPied)) return; if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out CreamPiedComponent? creamPied)) return;

View File

@@ -7,5 +7,8 @@ namespace Content.Shared.Body.Prototypes
{ {
[IdDataField] [IdDataField]
public string ID { get; } = default!; public string ID { get; } = default!;
[DataField("name", required: true)]
public string Name { get; } = default!;
} }
} }

View File

@@ -1,8 +1,11 @@
using System.Text.Json.Serialization; using System.Linq;
using System.Text.Json.Serialization;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Localizations;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Shared.Chemistry.Reagent namespace Content.Shared.Chemistry.Reagent
@@ -23,6 +26,10 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("conditions")] [DataField("conditions")]
public ReagentEffectCondition[]? Conditions; public ReagentEffectCondition[]? Conditions;
public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys); // => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
/// <summary> /// <summary>
/// What's the chance, from 0 to 1, that this effect will occur? /// What's the chance, from 0 to 1, that this effect will occur?
/// </summary> /// </summary>
@@ -42,6 +49,23 @@ namespace Content.Shared.Chemistry.Reagent
public virtual bool ShouldLog { get; } = false; public virtual bool ShouldLog { get; } = false;
public abstract void Effect(ReagentEffectArgs args); public abstract void Effect(ReagentEffectArgs args);
/// <summary>
/// Produces a localized, bbcode'd guidebook description for this effect.
/// </summary>
/// <returns></returns>
public string? GuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var effect = ReagentEffectGuidebookText(prototype, entSys);
if (effect is null)
return null;
return Loc.GetString(ReagentEffectFormat, ("effect", effect), ("chance", Probability),
("conditionCount", Conditions?.Length ?? 0),
("conditions",
ContentLocalizationManager.FormatList(Conditions?.Select(x => x.GuidebookExplanation(prototype)).ToList() ??
new List<string>())));
}
} }
public static class ReagentEffectExt public static class ReagentEffectExt

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Shared.Chemistry.Reagent namespace Content.Shared.Chemistry.Reagent
{ {
@@ -10,5 +11,12 @@ namespace Content.Shared.Chemistry.Reagent
[JsonPropertyName("id")] private protected string _id => this.GetType().Name; [JsonPropertyName("id")] private protected string _id => this.GetType().Name;
public abstract bool Condition(ReagentEffectArgs args); public abstract bool Condition(ReagentEffectArgs args);
/// <summary>
/// Effect explanations are of the form "[chance to] [action] when [condition] and [condition]"
/// </summary>
/// <param name="prototype"></param>
/// <returns></returns>
public abstract string GuidebookExplanation(IPrototypeManager prototype);
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Linq;
using System.Text.Json.Serialization;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.Body.Prototypes; using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
@@ -9,6 +10,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
@@ -157,6 +159,23 @@ namespace Content.Shared.Chemistry.Reagent
} }
} }
[Serializable, NetSerializable]
public struct ReagentGuideEntry
{
public string ReagentPrototype;
public Dictionary<string, ReagentEffectsGuideEntry>? GuideEntries;
public ReagentGuideEntry(ReagentPrototype proto, IPrototypeManager prototype, IEntitySystemManager entSys)
{
ReagentPrototype = proto.ID;
GuideEntries = proto.Metabolisms?
.Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
.ToDictionary(x => x.Key, x => x.Item2);
}
}
[DataDefinition] [DataDefinition]
public sealed class ReagentEffectsEntry public sealed class ReagentEffectsEntry
{ {
@@ -173,6 +192,30 @@ namespace Content.Shared.Chemistry.Reagent
[JsonPropertyName("effects")] [JsonPropertyName("effects")]
[DataField("effects", required: true)] [DataField("effects", required: true)]
public ReagentEffect[] Effects = default!; public ReagentEffect[] Effects = default!;
public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return new ReagentEffectsGuideEntry(MetabolismRate,
Effects
.Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
.Where(x => x is not null)
.Select(x => x!)
.ToArray());
}
}
[Serializable, NetSerializable]
public struct ReagentEffectsGuideEntry
{
public FixedPoint2 MetabolismRate;
public string[] EffectDescriptions;
public ReagentEffectsGuideEntry(FixedPoint2 metabolismRate, string[] effectDescriptions)
{
MetabolismRate = metabolismRate;
EffectDescriptions = effectDescriptions;
}
} }
[DataDefinition] [DataDefinition]

View File

@@ -0,0 +1,42 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry;
/// <summary>
/// This handles the chemistry guidebook and caching it.
/// </summary>
public abstract class SharedChemistryGuideDataSystem : EntitySystem
{
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
protected readonly Dictionary<string, ReagentGuideEntry> Registry = new();
public IReadOnlyDictionary<string, ReagentGuideEntry> ReagentGuideRegistry => Registry;
}
[Serializable, NetSerializable]
public sealed class ReagentGuideRegistryChangedEvent : EntityEventArgs
{
public ReagentGuideChangeset Changeset;
public ReagentGuideRegistryChangedEvent(ReagentGuideChangeset changeset)
{
Changeset = changeset;
}
}
[Serializable, NetSerializable]
public sealed class ReagentGuideChangeset
{
public Dictionary<string,ReagentGuideEntry> GuideEntries;
public HashSet<string> Removed;
public ReagentGuideChangeset(Dictionary<string, ReagentGuideEntry> guideEntries, HashSet<string> removed)
{
GuideEntries = guideEntries;
Removed = removed;
}
}

View File

@@ -1,4 +1,7 @@
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Robust.Shared.Utility;
namespace Content.Shared.Localizations namespace Content.Shared.Localizations
{ {
@@ -31,13 +34,96 @@ namespace Content.Shared.Localizations
_loc.AddFunction(culture, "UNITS", FormatUnits); _loc.AddFunction(culture, "UNITS", FormatUnits);
_loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args)); _loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args));
_loc.AddFunction(culture, "LOC", FormatLoc); _loc.AddFunction(culture, "LOC", FormatLoc);
_loc.AddFunction(culture, "NATURALFIXED", FormatNaturalFixed);
_loc.AddFunction(culture, "NATURALPERCENT", FormatNaturalPercent);
/*
* The following language functions are specific to the english localization. When working on your own
* localization you should NOT modify these, instead add new functions specific to your language/culture.
* This ensures the english translations continue to work as expected when fallbacks are needed.
*/
var cultureEn = new CultureInfo("en-US");
_loc.AddFunction(cultureEn, "MAKEPLURAL", FormatMakePlural);
_loc.AddFunction(cultureEn, "MANY", FormatMany);
}
private ILocValue FormatMany(LocArgs args)
{
var count = ((LocValueNumber) args.Args[1]).Value;
if (Math.Abs(count - 1) < 0.0001f)
{
return (LocValueString) args.Args[0];
}
else
{
return (LocValueString) FormatMakePlural(args);
}
}
private ILocValue FormatNaturalPercent(LocArgs args)
{
var number = ((LocValueNumber) args.Args[0]).Value * 100;
var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
formatter.NumberDecimalDigits = maxDecimals;
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.') + "%");
}
private ILocValue FormatNaturalFixed(LocArgs args)
{
var number = ((LocValueNumber) args.Args[0]).Value;
var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
formatter.NumberDecimalDigits = maxDecimals;
return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.'));
}
private static readonly Regex PluralEsRule = new("^.*(s|sh|ch|x|z)$");
private ILocValue FormatMakePlural(LocArgs args)
{
var text = ((LocValueString) args.Args[0]).Value;
var split = text.Split(" ", 1);
var firstWord = split[0];
if (PluralEsRule.IsMatch(firstWord))
{
if (split.Length == 1)
return new LocValueString($"{firstWord}es");
else
return new LocValueString($"{firstWord}es {split[1]}");
}
else
{
if (split.Length == 1)
return new LocValueString($"{firstWord}s");
else
return new LocValueString($"{firstWord}s {split[1]}");
}
}
// TODO: allow fluent to take in lists of strings so this can be a format function like it should be.
/// <summary>
/// Formats a list as per english grammar rules.
/// </summary>
public static string FormatList(List<string> list)
{
return list.Count switch
{
<= 0 => string.Empty,
1 => list[0],
2 => $"{list[0]} and {list[1]}",
> 2 => $"{string.Join(", ", list.GetRange(0, list.Count - 2))}, and {list[^1]}"
};
} }
private static ILocValue FormatLoc(LocArgs args) private static ILocValue FormatLoc(LocArgs args)
{ {
var id = ((LocValueString)args.Args[0]).Value; var id = ((LocValueString) args.Args[0]).Value;
return new LocValueString(Loc.GetString(id)); return new LocValueString(Loc.GetString(id, args.Options.Select(x => (x.Key, x.Value.Value!)).ToArray()));
} }
private static ILocValue FormatToString(CultureInfo culture, LocArgs args) private static ILocValue FormatToString(CultureInfo culture, LocArgs args)

View File

@@ -0,0 +1,50 @@
reagent-effect-condition-guidebook-total-damage =
{ $max ->
[2147483648] it has at least {NATURALFIXED($min, 2)} total damage
*[other] { $min ->
[0] it has at most {NATURALFIXED($max, 2)} total damage
*[other] it has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} total damage
}
}
reagent-effect-condition-guidebook-reagent-threshold =
{ $max ->
[2147483648] there's at least {NATURALFIXED($min, 2)}u of {$reagent}
*[other] { $min ->
[0] there's at most {NATURALFIXED($max, 2)}u of {$reagent}
*[other] there's between {NATURALFIXED($min, 2)}u and {NATURALFIXED($max, 2)}u of {$reagent}
}
}
reagent-effect-condition-guidebook-mob-state-condition =
the mob is { $state }
reagent-effect-condition-guidebook-solution-temperature =
the solution's temperature is { $max ->
[2147483648] at least {NATURALFIXED($min, 2)}k
*[other] { $min ->
[0] at most {NATURALFIXED($max, 2)}k
*[other] between {NATURALFIXED($min, 2)}k and {NATURALFIXED($max, 2)}k
}
}
reagent-effect-condition-guidebook-body-temperature =
the body's temperature is { $max ->
[2147483648] at least {NATURALFIXED($min, 2)}k
*[other] { $min ->
[0] at most {NATURALFIXED($max, 2)}k
*[other] between {NATURALFIXED($min, 2)}k and {NATURALFIXED($max, 2)}k
}
}
reagent-effect-condition-guidebook-organ-type =
the metabolizing organ { $shouldhave ->
[true] is
*[false] is not
} {INDEFINITE($name)} {$name} organ
reagent-effect-condition-guidebook-has-tag =
the target { $invert ->
[true] does not have
*[false] has
} the tag {$tag}

View File

@@ -0,0 +1,16 @@
guidebook-reagent-effect-description =
{$chance ->
[1] { $effect }
*[other] Has a { NATURALPERCENT($chance, 2) } chance to { $effect }
}{ $conditionCount ->
[0] .
*[other] {" "}when { $conditions }.
}
guidebook-reagent-name = [bold][color={$color}]{CAPITALIZE($name)}[/color][/bold]
guidebook-reagent-recipes-header = Recipe
guidebook-reagent-recipes-reagent-display = [bold]{$reagent}[/bold] \[{$ratio}\]
guidebook-reagent-recipes-mix = Mix
guidebook-reagent-effects-header = Effects
guidebook-reagent-effects-metabolism-group-rate = [bold]{$group}[/bold] [color=gray]({$rate} units per second)[/color]
guidebook-reagent-physical-description = Seems to be {$description}.

View File

@@ -0,0 +1,316 @@
-create-3rd-person =
{ $chance ->
[1] Creates
*[other] create
}
-cause-3rd-person =
{ $chance ->
[1] Causes
*[other] cause
}
-satiate-3rd-person =
{ $chance ->
[1] Satiates
*[other] satiate
}
reagent-effect-guidebook-create-entity-reaction-effect =
{ $chance ->
[1] Creates
*[other] create
} { $amount ->
[1] {INDEFINITE($entname)}
*[other] {$amount} {MAKEPLURAL($entname)}
}
reagent-effect-guidebook-explosion-reaction-effect =
{ $chance ->
[1] Causes
*[other] cause
} an explosion
reagent-effect-guidebook-foam-area-reaction-effect =
{ $chance ->
[1] Creates
*[other] create
} large quantities of foam
reagent-effect-guidebook-foam-area-reaction-effect =
{ $chance ->
[1] Creates
*[other] create
} large quantities of smoke
reagent-effect-guidebook-satiate-thirst =
{ $chance ->
[1] Satiates
*[other] satiate
} { $relative ->
[1] thirst averagely
*[other] thirst at {NATURALFIXED($relative, 3)}x the average rate
}
reagent-effect-guidebook-satiate-hunger =
{ $chance ->
[1] Satiates
*[other] satiate
} { $relative ->
[1] hunger averagely
*[other] hunger at {NATURALFIXED($relative, 3)}x the average rate
}
reagent-effect-guidebook-health-change =
{ $chance ->
[1] { $healsordeals ->
[heals] Heals
[deals] Deals
*[both] Modifies health by
}
*[other] { $healsordeals ->
[heals] heal
[deals] deal
*[both] modify health by
}
} { $changes }
reagent-effect-guidebook-status-effect =
{ $type ->
[add] { $chance ->
[1] Causes
*[other] cause
} {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
*[set] { $chance ->
[1] Causes
*[other] cause
} {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
[remove]{ $chance ->
[1] Removes
*[other] remove
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
}
reagent-effect-guidebook-activate-artifact =
{ $chance ->
[1] Attempts
*[other] attempt
} to activate an artifact
reagent-effect-guidebook-set-solution-temperature-effect =
{ $chance ->
[1] Sets
*[other] set
} the solution temperature to exactly {NATURALFIXED($temperature, 2)}k
reagent-effect-guidebook-adjust-solution-temperature-effect =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] Removes
}
*[other]
{ $deltasign ->
[1] add
*[-1] remove
}
} heat from the solution until it reaches { $deltasign ->
[1] at most {NATURALFIXED($maxtemp, 2)}k
*[-1] at least {NATURALFIXED($mintemp, 2)}k
}
reagent-effect-guidebook-adjust-reagent-reagent =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] Removes
}
*[other]
{ $deltasign ->
[1] add
*[-1] remove
}
} {NATURALFIXED($amount, 2)}u of {$reagent} { $deltasign ->
[1] to
*[-1] from
} the solution
reagent-effect-guidebook-adjust-reagent-group =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] Removes
}
*[other]
{ $deltasign ->
[1] add
*[-1] remove
}
} {NATURALFIXED($amount, 2)}u of reagents in the group {$group} { $deltasign ->
[1] to
*[-1] from
} the solution
reagent-effect-guidebook-adjust-temperature =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] Removes
}
*[other]
{ $deltasign ->
[1] add
*[-1] remove
}
} {POWERJOULES($amount)} of heat { $deltasign ->
[1] to
*[-1] from
} the body it's in
reagent-effect-guidebook-chem-cause-disease =
{ $chance ->
[1] Causes
*[other] cause
} the disease { $disease }
reagent-effect-guidebook-chem-cause-random-disease =
{ $chance ->
[1] Causes
*[other] cause
} the diseases { $diseases }
reagent-effect-guidebook-jittering =
{ $chance ->
[1] Causes
*[other] cause
} jittering
reagent-effect-guidebook-chem-clean-bloodstream =
{ $chance ->
[1] Cleanses
*[other] cleanse
} the bloodstream of other chemicals
reagent-effect-guidebook-cure-disease =
{ $chance ->
[1] Cures
*[other] cure
} diseases
reagent-effect-guidebook-cure-eye-damage =
{ $chance ->
[1] { $deltasign ->
[1] Heals
*[-1] Deals
}
*[other]
{ $deltasign ->
[1] heal
*[-1] deal
}
} eye damage
reagent-effect-guidebook-chem-vomit =
{ $chance ->
[1] Causes
*[other] cause
} vomiting
reagent-effect-guidebook-create-gas =
{ $chance ->
[1] Creates
*[other] create
} { $moles } { $moles ->
[1] mole
*[other] moles
} of { $gas }
reagent-effect-guidebook-drunk =
{ $chance ->
[1] Causes
*[other] cause
} drunkness
reagent-effect-guidebook-electrocute =
{ $chance ->
[1] Electrocutes
*[other] electrocute
} the metabolizer for {NATURALFIXED($time, 3)} {MANY("second", $time)}
reagent-effect-guidebook-extinguish-reaction =
{ $chance ->
[1] Extinguishes
*[other] extinguish
} fire
reagent-effect-guidebook-flammable-reaction =
{ $chance ->
[1] Increases
*[other] increase
} flammability
reagent-effect-guidebook-ignite =
{ $chance ->
[1] Ignites
*[other] ignite
} the metabolizer
reagent-effect-guidebook-make-sentient =
{ $chance ->
[1] Makes
*[other] make
} the metabolizer sentient
reagent-effect-guidebook-modify-bleed-amount =
{ $chance ->
[1] { $deltasign ->
[1] Induces
*[-1] Reduces
}
*[other] { $deltasign ->
[1] induce
*[-1] reduce
}
} bleeding
reagent-effect-guidebook-modify-blood-level =
{ $chance ->
[1] { $deltasign ->
[1] Increases
*[-1] Decreases
}
*[other] { $deltasign ->
[1] increases
*[-1] decreases
}
} blood level
reagent-effect-guidebook-paralyze =
{ $chance ->
[1] Paralyzes
*[other] paralyze
} the metabolizer for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
reagent-effect-guidebook-movespeed-modifier =
{ $chance ->
[1] Modifies
*[other] modify
} movement speed by {NATURALFIXED($walkspeed, 3)}x for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
reagent-effect-guidebook-reset-narcolepsy =
{ $chance ->
[1] Temporarily staves
*[other] temporarily stave
} off narcolepsy
reagent-effect-guidebook-wash-cream-pie-reaction =
{ $chance ->
[1] Washes
*[other] wash
} off cream pie from one's face
reagent-effect-guidebook-missing =
{ $chance ->
[1] Causes
*[other] cause
} an unknown effect as nobody has written this effect yet

View File

@@ -0,0 +1,5 @@
health-change-display =
{ $deltasign ->
[-1] [color=green]{NATURALFIXED($amount, 2)}[/color] {$kind}
*[1] [color=red]{NATURALFIXED($amount, 2)}[/color] {$kind}
}

View File

@@ -0,0 +1,11 @@
reagent-effect-status-effect-Stun = stunning
reagent-effect-status-effect-KnockedDown = knockdown
reagent-effect-status-effect-Jitter = jittering
reagent-effect-status-effect-TemporaryBlindness = blindess
reagent-effect-status-effect-SeeingRainbows = hallucinations
reagent-effect-status-effect-Muted = inability to speak
reagent-effect-status-effect-Stutter = stuttering
reagent-effect-status-effect-ForcedSleep = unconsciousness
reagent-effect-status-effect-Drunk = drunkness
reagent-effect-status-effect-PressureImmunity = pressure immunity
reagent-effect-status-effect-Pacified = combat pacification

View File

@@ -14,6 +14,7 @@ guide-entry-radio = Radio
guide-entry-jobs = Jobs guide-entry-jobs = Jobs
guide-entry-salvage = Salvage guide-entry-salvage = Salvage
guide-entry-survival = Survival guide-entry-survival = Survival
guide-entry-chemicals = Chemicals
guide-entry-ss14 = Space Station 14 guide-entry-ss14 = Space Station 14
guide-entry-janitorial = Janitorial guide-entry-janitorial = Janitorial

View File

@@ -3,24 +3,32 @@
- type: metabolizerType - type: metabolizerType
id: Animal id: Animal
name: animal
- type: metabolizerType - type: metabolizerType
id: Dragon id: Dragon
name: dragon
- type: metabolizerType - type: metabolizerType
id: Human id: Human
name: human
- type: metabolizerType - type: metabolizerType
id: Slime id: Slime
name: slime
- type: metabolizerType - type: metabolizerType
id: Vox id: Vox
name: vox
- type: metabolizerType - type: metabolizerType
id: Rat id: Rat
name: rat
- type: metabolizerType - type: metabolizerType
id: Plant id: Plant
name: plant
- type: metabolizerType - type: metabolizerType
id: Dwarf id: Dwarf
name: dwarf

View File

@@ -32,3 +32,6 @@
- type: UpgradePowerDraw - type: UpgradePowerDraw
powerDrawMultiplier: 0.75 powerDrawMultiplier: 0.75
scaling: Exponential scaling: Exponential
- type: GuideHelp
guides:
- Chemicals

View File

@@ -15,6 +15,11 @@
name: guide-entry-survival name: guide-entry-survival
text: "/ServerInfo/Guidebook/Survival.xml" text: "/ServerInfo/Guidebook/Survival.xml"
- type: guideEntry
id: Chemicals
name: guide-entry-chemicals
text: "/ServerInfo/Guidebook/Chemicals.xml"
- type: guideEntry - type: guideEntry
id: Janitorial id: Janitorial
name: guide-entry-janitorial name: guide-entry-janitorial

View File

@@ -6,3 +6,4 @@
- Controls - Controls
- Jobs - Jobs
- Survival - Survival
- Chemicals

View File

@@ -0,0 +1,34 @@
<Document>
# Chemicals
Chemicals are a powerful tool that can cause a variety of effects when consumed. Some can be found in plants, purchased from cargo, or be synthesized through combination with other chemicals.
Knowing different types of chemicals and their effects is important for being able to manage injury and danger.
## Elements
<GuideReagentGroupEmbed Group="Elements"/>
## Medicine
<GuideReagentGroupEmbed Group="Medicine"/>
## Narcotics
<GuideReagentGroupEmbed Group="Narcotics"/>
## Pyrotechnics
<GuideReagentGroupEmbed Group="Pyrotechnic"/>
## Toxins
<GuideReagentGroupEmbed Group="Toxins"/>
## Foods
<GuideReagentGroupEmbed Group="Foods"/>
## Botanical
<GuideReagentGroupEmbed Group="Botanical"/>
## Biological
<GuideReagentGroupEmbed Group="Biological"/>
## Other
<GuideReagentGroupEmbed Group="Unknown"/>
</Document>

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B