6
2025-08-05 10:00:54 +03:00

189 lines
6.5 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client._NF.Medical.EntitySystems;
using Content.Client.Chemistry.EntitySystems;
using Content.Client.Guidebook.Controls;
using Content.Client.Guidebook.Richtext;
using Content.Client.Message;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
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._NF.Guidebook.Controls;
/// <summary>
/// Control for embedding a medical recipe into a guidebook.
/// </summary>
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideMedicalEmbed : BoxContainer, IDocumentTag, ISearchableControl
{
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly MedicalGuideDataSystem _medicalGuideData;
private readonly ISawmill _logger = default!;
public GuideMedicalEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_medicalGuideData = _systemManager.GetEntitySystem<MedicalGuideDataSystem>();
_logger = Logger.GetSawmill("medical guide");
MouseFilter = MouseFilterMode.Stop;
}
public GuideMedicalEmbed(MedicalGuideEntry entry) : this()
{
GenerateControl(entry);
}
public bool CheckMatchesSearch(string query)
{
return ResultName.GetMessage()?.Contains(query, StringComparison.InvariantCultureIgnoreCase) == true
|| Description.GetMessage()?.Contains(query, StringComparison.InvariantCultureIgnoreCase) == true;
}
public void SetHiddenState(bool state, string query)
{
Visible = CheckMatchesSearch(query) ? state : !state;
}
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = null;
if (!args.TryGetValue("Result", out var id))
{
_logger.Error("Result embed tag is missing food prototype argument.");
return false;
}
if (!_medicalGuideData.TryGetData(id, out var data))
{
_logger.Warning($"Specified result prototype \"{id}\" does not have any known sources.");
return false;
}
GenerateControl(data);
control = this;
return true;
}
private void GenerateControl(MedicalGuideEntry data)
{
_prototype.TryIndex(data.Result, out var proto);
if (proto == null)
{
ResultName.SetMarkup(Loc.GetString("guidebook-food-unknown-proto", ("id", data.Result)));
return;
}
var composition = data.Composition
.Select(it => _prototype.TryIndex<ReagentPrototype>(it.Reagent.Prototype, out var reagent) ? (reagent, it.Quantity) : (null, 0))
.Where(it => it.reagent is not null)
.Cast<(ReagentPrototype, FixedPoint2)>()
.ToList();
#region Colors
CalculateColors(composition, out var textColor, out var backgroundColor);
NameBackground.PanelOverride = new StyleBoxFlat
{
BackgroundColor = backgroundColor
};
ResultName.SetMarkup(Loc.GetString("guidebook-food-name", ("color", textColor), ("name", proto.Name)));
#endregion
#region Recipes
if (data.Recipes.Length > 0)
RecipesContainer.Visible = true;
foreach (var recipe in data.Recipes.OrderBy(it => it.OutputCount))
{
var control = new GuideMedicalSource(proto, recipe, _prototype);
RecipesDescriptionContainer.AddChild(control);
}
#endregion
#region Composition
if (composition.Count > 0)
CompositionContainer.Visible = true;
foreach (var (reagent, quantity) in composition)
{
var control = new GuideMedicalComposition(reagent, quantity);
CompositionDescriptionContainer.AddChild(control);
}
#endregion
#region Damage
var damageDict = data.Healing?.DamageDict ?? new();
if (damageDict.Count > 0)
DamageContainer.Visible = true;
foreach (var (damageType, damage) in damageDict)
{
if (_prototype.TryIndex<DamageTypePrototype>(damageType, out var damageProto))
{
var control = new GuideMedicalDamage(damageProto, -damage); // Negative damage means positive healing
DamageDescriptionContainer.AddChild(control);
}
else if (_prototype.TryIndex<DamageGroupPrototype>(damageType, out var groupProto))
{
var control = new GuideMedicalDamage(groupProto, -damage); // Negative damage means positive healing
DamageDescriptionContainer.AddChild(control);
}
}
#endregion
FormattedMessage description = new();
description.AddText(proto?.Description ?? string.Empty);
// Cannot describe food flavor or smth beause food is entirely server-side
Description.SetMessage(description);
}
private void CalculateColors(List<(ReagentPrototype, FixedPoint2)> composition, out Color text, out Color background)
{
// Background color is calculated as the weighted average of the colors of the composition.
// Text color is determined based on background luminosity.
float r = 0, g = 0, b = 0;
FixedPoint2 weight = 0;
foreach (var (proto, quantity) in composition)
{
var tcolor = proto.SubstanceColor;
var prevalence =
quantity <= 0 ? 0f
: weight == 0f ? 1f
: (quantity / (weight + quantity)).Float();
r = r * (1 - prevalence) + tcolor.R * prevalence;
g = g * (1 - prevalence) + tcolor.G * prevalence;
b = b * (1 - prevalence) + tcolor.B * prevalence;
if (quantity > 0)
weight += quantity;
}
// Copied from GuideReagentEmbed which was probably copied from stackoverflow. This is the formula for color luminosity.
var lum = 0.2126f * r + 0.7152f * g + 0.0722f;
background = new Color(r, g, b);
text = lum > 0.5f ? Color.Black : Color.White;
}
}