ReagentPrototype.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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(serverOnly: true)]
  103. public FrozenDictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsEntry>? Metabolisms;
  104. [DataField(serverOnly: true)]
  105. public Dictionary<ProtoId<ReactiveGroupPrototype>, ReactiveReagentEffectEntry>? ReactiveEffects;
  106. [DataField(serverOnly: true)]
  107. public List<ITileReaction> TileReactions = new(0);
  108. [DataField("plantMetabolism", serverOnly: true)]
  109. public List<EntityEffect> PlantMetabolisms = new(0);
  110. [DataField]
  111. public float PricePerUnit;
  112. [DataField]
  113. public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepWater", AudioParams.Default.WithVolume(6));
  114. public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
  115. {
  116. var removed = FixedPoint2.Zero;
  117. if (tile.Tile.IsEmpty)
  118. return removed;
  119. foreach (var reaction in TileReactions)
  120. {
  121. removed += reaction.TileReact(tile, this, reactVolume - removed, entityManager, data);
  122. if (removed > reactVolume)
  123. throw new Exception("Removed more than we have!");
  124. if (removed == reactVolume)
  125. break;
  126. }
  127. return removed;
  128. }
  129. public void ReactionPlant(EntityUid? plantHolder, ReagentQuantity amount, Solution solution)
  130. {
  131. if (plantHolder == null)
  132. return;
  133. var entMan = IoCManager.Resolve<IEntityManager>();
  134. var random = IoCManager.Resolve<IRobustRandom>();
  135. var args = new EntityEffectReagentArgs(plantHolder.Value, entMan, null, solution, amount.Quantity, this, null, 1f);
  136. foreach (var plantMetabolizable in PlantMetabolisms)
  137. {
  138. if (!plantMetabolizable.ShouldApply(args, random))
  139. continue;
  140. if (plantMetabolizable.ShouldLog)
  141. {
  142. var entity = args.TargetEntity;
  143. entMan.System<SharedAdminLogSystem>().Add(LogType.ReagentEffect, plantMetabolizable.LogImpact,
  144. $"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}");
  145. }
  146. plantMetabolizable.Effect(args);
  147. }
  148. }
  149. }
  150. [Serializable, NetSerializable]
  151. public struct ReagentGuideEntry
  152. {
  153. public string ReagentPrototype;
  154. public Dictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsGuideEntry>? GuideEntries;
  155. public List<string>? PlantMetabolisms = null;
  156. public ReagentGuideEntry(ReagentPrototype proto, IPrototypeManager prototype, IEntitySystemManager entSys)
  157. {
  158. ReagentPrototype = proto.ID;
  159. GuideEntries = proto.Metabolisms?
  160. .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
  161. .ToDictionary(x => x.Key, x => x.Item2);
  162. if (proto.PlantMetabolisms.Count > 0)
  163. {
  164. PlantMetabolisms = new List<string> (proto.PlantMetabolisms
  165. .Select(x => x.GuidebookEffectDescription(prototype, entSys))
  166. .Where(x => x is not null)
  167. .Select(x => x!)
  168. .ToArray());
  169. }
  170. }
  171. }
  172. [DataDefinition]
  173. public sealed partial class ReagentEffectsEntry
  174. {
  175. /// <summary>
  176. /// Amount of reagent to metabolize, per metabolism cycle.
  177. /// </summary>
  178. [JsonPropertyName("rate")]
  179. [DataField("metabolismRate")]
  180. public FixedPoint2 MetabolismRate = FixedPoint2.New(0.5f);
  181. /// <summary>
  182. /// A list of effects to apply when these reagents are metabolized.
  183. /// </summary>
  184. [JsonPropertyName("effects")]
  185. [DataField("effects", required: true)]
  186. public EntityEffect[] Effects = default!;
  187. public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
  188. {
  189. return new ReagentEffectsGuideEntry(MetabolismRate,
  190. Effects
  191. .Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
  192. .Where(x => x is not null)
  193. .Select(x => x!)
  194. .ToArray());
  195. }
  196. }
  197. [Serializable, NetSerializable]
  198. public struct ReagentEffectsGuideEntry
  199. {
  200. public FixedPoint2 MetabolismRate;
  201. public string[] EffectDescriptions;
  202. public ReagentEffectsGuideEntry(FixedPoint2 metabolismRate, string[] effectDescriptions)
  203. {
  204. MetabolismRate = metabolismRate;
  205. EffectDescriptions = effectDescriptions;
  206. }
  207. }
  208. [DataDefinition]
  209. public sealed partial class ReactiveReagentEffectEntry
  210. {
  211. [DataField("methods", required: true)]
  212. public HashSet<ReactionMethod> Methods = default!;
  213. [DataField("effects", required: true)]
  214. public EntityEffect[] Effects = default!;
  215. }
  216. }