ReagentPrototype.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. using System.Collections.Frozen;
  2. using System.Linq;
  3. using System.Text.Json.Serialization;
  4. using Content.Shared.Administration.Logs;
  5. using Content.Shared.Body.Prototypes;
  6. using Content.Shared.Chemistry.Components;
  7. using Content.Shared.Chemistry.Reaction;
  8. using Content.Shared.EntityEffects;
  9. using Content.Shared.Database;
  10. using Content.Shared.FixedPoint;
  11. using Content.Shared.Nutrition;
  12. using Robust.Shared.Audio;
  13. using Robust.Shared.Map;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Random;
  16. using Robust.Shared.Serialization;
  17. using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
  18. using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
  19. using Robust.Shared.Utility;
  20. namespace Content.Shared.Chemistry.Reagent
  21. {
  22. [Prototype]
  23. [DataDefinition]
  24. public sealed partial class ReagentPrototype : IPrototype, IInheritingPrototype
  25. {
  26. [ViewVariables]
  27. [IdDataField]
  28. public string ID { get; private set; } = default!;
  29. [DataField(required: true)]
  30. private LocId Name { get; set; }
  31. [ViewVariables(VVAccess.ReadOnly)]
  32. public string LocalizedName => Loc.GetString(Name);
  33. [DataField]
  34. public string Group { get; private set; } = "Unknown";
  35. [ParentDataField(typeof(AbstractPrototypeIdArraySerializer<ReagentPrototype>))]
  36. public string[]? Parents { get; private set; }
  37. [NeverPushInheritance]
  38. [AbstractDataField]
  39. public bool Abstract { get; private set; }
  40. [DataField("desc", required: true)]
  41. private LocId Description { get; set; }
  42. [ViewVariables(VVAccess.ReadOnly)]
  43. public string LocalizedDescription => Loc.GetString(Description);
  44. [DataField("physicalDesc", required: true)]
  45. private LocId PhysicalDescription { get; set; } = default!;
  46. [ViewVariables(VVAccess.ReadOnly)]
  47. public string LocalizedPhysicalDescription => Loc.GetString(PhysicalDescription);
  48. /// <summary>
  49. /// Is this reagent recognizable to the average spaceman (water, welding fuel, ketchup, etc)?
  50. /// </summary>
  51. [DataField]
  52. public bool Recognizable;
  53. [DataField]
  54. public ProtoId<FlavorPrototype>? Flavor;
  55. /// <summary>
  56. /// There must be at least this much quantity in a solution to be tasted.
  57. /// </summary>
  58. [DataField]
  59. public FixedPoint2 FlavorMinimum = FixedPoint2.New(0.1f);
  60. [DataField("color")]
  61. public Color SubstanceColor { get; private set; } = Color.White;
  62. /// <summary>
  63. /// The specific heat of the reagent.
  64. /// How much energy it takes to heat one unit of this reagent by one Kelvin.
  65. /// </summary>
  66. [DataField]
  67. public float SpecificHeat { get; private set; } = 1.0f;
  68. [DataField]
  69. public float? BoilingPoint { get; private set; }
  70. [DataField]
  71. public float? MeltingPoint { get; private set; }
  72. [DataField]
  73. public SpriteSpecifier? MetamorphicSprite { get; private set; } = null;
  74. [DataField]
  75. public int MetamorphicMaxFillLevels { get; private set; } = 0;
  76. [DataField]
  77. public string? MetamorphicFillBaseName { get; private set; } = null;
  78. [DataField]
  79. public bool MetamorphicChangeColor { get; private set; } = true;
  80. /// <summary>
  81. /// If this reagent is part of a puddle is it slippery.
  82. /// </summary>
  83. [DataField]
  84. public bool Slippery;
  85. /// <summary>
  86. /// How easily this reagent becomes fizzy when aggitated.
  87. /// 0 - completely flat, 1 - fizzes up when nudged.
  88. /// </summary>
  89. [DataField]
  90. public float Fizziness;
  91. /// <summary>
  92. /// How much reagent slows entities down if it's part of a puddle.
  93. /// 0 - no slowdown; 1 - can't move.
  94. /// </summary>
  95. [DataField]
  96. public float Viscosity;
  97. /// <summary>
  98. /// Should this reagent work on the dead?
  99. /// </summary>
  100. [DataField]
  101. public bool WorksOnTheDead;
  102. [DataField]
  103. public bool? WorksOnUnconscious;
  104. [DataField(serverOnly: true)]
  105. public FrozenDictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsEntry>? Metabolisms;
  106. [DataField(serverOnly: true)]
  107. public Dictionary<ProtoId<ReactiveGroupPrototype>, ReactiveReagentEffectEntry>? ReactiveEffects;
  108. [DataField(serverOnly: true)]
  109. public List<ITileReaction> TileReactions = new(0);
  110. [DataField("plantMetabolism", serverOnly: true)]
  111. public List<EntityEffect> PlantMetabolisms = new(0);
  112. [DataField]
  113. public float PricePerUnit;
  114. [DataField]
  115. public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepWater", AudioParams.Default.WithVolume(6));
  116. public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
  117. {
  118. var removed = FixedPoint2.Zero;
  119. if (tile.Tile.IsEmpty)
  120. return removed;
  121. foreach (var reaction in TileReactions)
  122. {
  123. removed += reaction.TileReact(tile, this, reactVolume - removed, entityManager, data);
  124. if (removed > reactVolume)
  125. throw new Exception("Removed more than we have!");
  126. if (removed == reactVolume)
  127. break;
  128. }
  129. return removed;
  130. }
  131. public void ReactionPlant(EntityUid? plantHolder, ReagentQuantity amount, Solution solution)
  132. {
  133. if (plantHolder == null)
  134. return;
  135. var entMan = IoCManager.Resolve<IEntityManager>();
  136. var random = IoCManager.Resolve<IRobustRandom>();
  137. var args = new EntityEffectReagentArgs(plantHolder.Value, entMan, null, solution, amount.Quantity, this, null, 1f);
  138. foreach (var plantMetabolizable in PlantMetabolisms)
  139. {
  140. if (!plantMetabolizable.ShouldApply(args, random))
  141. continue;
  142. if (plantMetabolizable.ShouldLog)
  143. {
  144. var entity = args.TargetEntity;
  145. entMan.System<SharedAdminLogSystem>().Add(LogType.ReagentEffect, plantMetabolizable.LogImpact,
  146. $"Plant metabolism effect {plantMetabolizable.GetType().Name:effect} of reagent {ID:reagent} applied on entity {entMan.ToPrettyString(entity):entity} at {entMan.GetComponent<TransformComponent>(entity).Coordinates:coordinates}");
  147. }
  148. plantMetabolizable.Effect(args);
  149. }
  150. }
  151. }
  152. [Serializable, NetSerializable]
  153. public struct ReagentGuideEntry
  154. {
  155. public string ReagentPrototype;
  156. public Dictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsGuideEntry>? GuideEntries;
  157. public List<string>? PlantMetabolisms = null;
  158. public ReagentGuideEntry(ReagentPrototype proto, IPrototypeManager prototype, IEntitySystemManager entSys)
  159. {
  160. ReagentPrototype = proto.ID;
  161. GuideEntries = proto.Metabolisms?
  162. .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
  163. .ToDictionary(x => x.Key, x => x.Item2);
  164. if (proto.PlantMetabolisms.Count > 0)
  165. {
  166. PlantMetabolisms = new List<string>(proto.PlantMetabolisms
  167. .Select(x => x.GuidebookEffectDescription(prototype, entSys))
  168. .Where(x => x is not null)
  169. .Select(x => x!)
  170. .ToArray());
  171. }
  172. }
  173. }
  174. [DataDefinition]
  175. public sealed partial class ReagentEffectsEntry
  176. {
  177. /// <summary>
  178. /// Amount of reagent to metabolize, per metabolism cycle.
  179. /// </summary>
  180. [JsonPropertyName("rate")]
  181. [DataField("metabolismRate")]
  182. public FixedPoint2 MetabolismRate = FixedPoint2.New(0.5f);
  183. /// <summary>
  184. /// A list of effects to apply when these reagents are metabolized.
  185. /// </summary>
  186. [JsonPropertyName("effects")]
  187. [DataField("effects", required: true)]
  188. public EntityEffect[] Effects = default!;
  189. public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
  190. {
  191. return new ReagentEffectsGuideEntry(MetabolismRate,
  192. Effects
  193. .Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
  194. .Where(x => x is not null)
  195. .Select(x => x!)
  196. .ToArray());
  197. }
  198. }
  199. [Serializable, NetSerializable]
  200. public struct ReagentEffectsGuideEntry
  201. {
  202. public FixedPoint2 MetabolismRate;
  203. public string[] EffectDescriptions;
  204. public ReagentEffectsGuideEntry(FixedPoint2 metabolismRate, string[] effectDescriptions)
  205. {
  206. MetabolismRate = metabolismRate;
  207. EffectDescriptions = effectDescriptions;
  208. }
  209. }
  210. [DataDefinition]
  211. public sealed partial class ReactiveReagentEffectEntry
  212. {
  213. [DataField("methods", required: true)]
  214. public HashSet<ReactionMethod> Methods = default!;
  215. [DataField("effects", required: true)]
  216. public EntityEffect[] Effects = default!;
  217. }
  218. }