Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/GameSession/MedicalClinic.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
9 
10 namespace Barotrauma
11 {
12  internal sealed partial class MedicalClinic
13  {
14  private const int RateLimitMaxRequests = 20,
15  RateLimitExpiry = 5;
16 
17  public enum NetworkHeader
18  {
19  REQUEST_AFFLICTIONS,
20  AFFLICTION_UPDATE,
21  UNSUBSCRIBE_ME,
22  REQUEST_PENDING,
23  ADD_PENDING,
24  REMOVE_PENDING,
25  CLEAR_PENDING,
26  HEAL_PENDING,
27  ADD_EVERYTHING_TO_PENDING
28  }
29 
30  public enum AfflictionSeverity
31  {
32  Low,
33  Medium,
34  High
35  }
36 
37  public enum MessageFlag
38  {
39  Response, // responding to your request
40  Announce // responding to someone else's request
41  }
42 
43  public enum HealRequestResult
44  {
45  Unknown, // everything is not ok
46  Success, // everything ok
47  InsufficientFunds, // not enough money
48  Refused // the outpost has refused to provide medical assistance
49  }
50 
51  [NetworkSerialize]
52  public readonly record struct NetHealRequest(HealRequestResult Result) : INetSerializableStruct;
53 
54  [NetworkSerialize]
55  public readonly record struct NetRemovedAffliction(NetCrewMember CrewMember, NetAffliction Affliction) : INetSerializableStruct;
56 
57  public struct NetAffliction : INetSerializableStruct
58  {
60  public Identifier Identifier;
61 
63  public ushort Strength;
64 
66  public int VitalityDecrease;
67 
69  public ushort Price;
70 
71  public void SetAffliction(Affliction affliction, CharacterHealth characterHealth)
72  {
73  Identifier = affliction.Identifier;
74  Strength = (ushort)Math.Ceiling(affliction.Strength);
75  Price = (ushort)(affliction.Prefab.BaseHealCost + Strength * affliction.Prefab.HealCostMultiplier);
76  VitalityDecrease = (int)affliction.GetVitalityDecrease(characterHealth);
77  }
78 
79  private AfflictionPrefab? cachedPrefab;
80 
82  {
83  get
84  {
85  if (cachedPrefab is { } cached) { return cached; }
86 
87  foreach (AfflictionPrefab prefab in AfflictionPrefab.List)
88  {
89  if (prefab.Identifier == Identifier)
90  {
91  cachedPrefab = prefab;
92  return prefab;
93  }
94  }
95 
96  return null;
97  }
98  set
99  {
100  cachedPrefab = value;
101  Identifier = value?.Identifier ?? Identifier.Empty;
102  Strength = 0;
103  Price = 0;
104  }
105  }
106 
107  public readonly bool AfflictionEquals(AfflictionPrefab prefab)
108  {
109  return prefab.Identifier == Identifier;
110  }
111 
112  public readonly bool AfflictionEquals(NetAffliction affliction)
113  {
114  return affliction.Identifier == Identifier;
115  }
116  }
117 
118  public record struct NetCrewMember : INetSerializableStruct
119  {
121  public int CharacterInfoID;
122 
124  public ImmutableArray<NetAffliction> Afflictions;
125 
127  {
128  CharacterInfoID = info.ID;
129  Afflictions = ImmutableArray<NetAffliction>.Empty;
130  }
131 
132  public NetCrewMember(CharacterInfo info, ImmutableArray<NetAffliction> afflictions): this(info)
133  {
134  Afflictions = afflictions;
135  }
136 
137  public readonly CharacterInfo? FindCharacterInfo(ImmutableArray<CharacterInfo> crew)
138  {
139  foreach (CharacterInfo info in crew)
140  {
141  if (info.ID == CharacterInfoID)
142  {
143  return info;
144  }
145  }
146 
147  return null;
148  }
149 
150  public readonly bool CharacterEquals(NetCrewMember crewMember)
151  {
152  return crewMember.CharacterInfoID == CharacterInfoID;
153  }
154  }
155 
156  public readonly List<NetCrewMember> PendingHeals = new List<NetCrewMember>();
157 
158  public Action? OnUpdate;
159 
160  private readonly CampaignMode? campaign;
161 
165  private readonly HashSet<Character> charactersWithAfflictionChanges = new HashSet<Character>();
166 
167  private float processAfflictionChangesTimer;
168  private const float ProcessAfflictionChangesInterval = 1.0f;
169 
170  public MedicalClinic(CampaignMode campaign)
171  {
172  this.campaign = campaign;
173 #if CLIENT
174  campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(MedicalClinic).ToIdentifier(), OnMoneyChanged);
175 #endif
176  }
177 
178  private static bool IsOutpostInCombat()
179  {
180  if (Level.Loaded is not { Type: LevelData.LevelType.Outpost }) { return false; }
181 
182  IEnumerable<Character> crew = GetCrewCharacters().Where(static c => c.Character != null).Select(static c => c.Character).ToImmutableHashSet();
183 
184  foreach (Character npc in Character.CharacterList.Where(static c => c.TeamID == CharacterTeamType.FriendlyNPC))
185  {
186  bool isInCombatWithCrew = !npc.IsInstigator && npc.AIController is HumanAIController { ObjectiveManager: { CurrentObjective: AIObjectiveCombat combatObjective } } && crew.Contains(combatObjective.Enemy);
187  if (isInCombatWithCrew) { return true; }
188  }
189 
190  return false;
191  }
192 
193  private HealRequestResult HealAllPending(bool force = false, Client? client = null)
194  {
195  int totalCost = GetTotalCost();
196  if (!force)
197  {
198  if (IsOutpostInCombat()) { return HealRequestResult.Refused; }
199  if (!(campaign?.TryPurchase(client, totalCost) ?? false)) { return HealRequestResult.InsufficientFunds; }
200  }
201 
202  ImmutableArray<CharacterInfo> crew = GetCrewCharacters();
203  foreach (NetCrewMember crewMember in PendingHeals)
204  {
205  CharacterInfo? targetCharacter = crewMember.FindCharacterInfo(crew);
206  if (!(targetCharacter?.Character is { CharacterHealth: { } health })) { continue; }
207 
208  foreach (NetAffliction affliction in crewMember.Afflictions)
209  {
210  health.ReduceAfflictionOnAllLimbs(affliction.Identifier, affliction.Prefab?.MaxStrength ?? affliction.Strength);
211  }
212  }
213 
214  ClearPendingHeals();
215 
216  return HealRequestResult.Success;
217  }
218 
219  private void ClearPendingHeals()
220  {
221  PendingHeals.Clear();
222  }
223 
224  private void AddEverythingToPending()
225  {
226  foreach (CharacterInfo info in GetCrewCharacters())
227  {
228  if (info.Character?.CharacterHealth is not { } health) { continue; }
229 
230  var afflictions = GetAllAfflictions(health);
231 
232  if (afflictions.Length is 0) { continue; }
233 
234  InsertPendingCrewMember(new NetCrewMember(info, afflictions));
235  }
236  }
237 
238  private void RemovePendingAffliction(NetCrewMember crewMember, NetAffliction affliction)
239  {
240  foreach (NetCrewMember listMember in PendingHeals.ToList())
241  {
242  PendingHeals.Remove(listMember);
243  NetCrewMember pendingMember = listMember;
244 
245  if (pendingMember.CharacterEquals(crewMember))
246  {
247  List<NetAffliction> newAfflictions = new List<NetAffliction>();
248  foreach (NetAffliction pendingAffliction in pendingMember.Afflictions)
249  {
250  if (pendingAffliction.AfflictionEquals(affliction)) { continue; }
251 
252  newAfflictions.Add(pendingAffliction);
253  }
254 
255  pendingMember.Afflictions = newAfflictions.ToImmutableArray();
256  }
257 
258  if (!pendingMember.Afflictions.Any()) { continue; }
259 
260  PendingHeals.Add(pendingMember);
261  }
262  }
263 
264  private void InsertPendingCrewMember(NetCrewMember crewMember)
265  {
266  if (PendingHeals.FirstOrNull(m => m.CharacterEquals(crewMember)) is { } foundHeal)
267  {
268  PendingHeals.Remove(foundHeal);
269  }
270 
271  PendingHeals.Add(crewMember);
272  }
273 
274  public static bool IsHealable(Affliction affliction)
275  {
276  return affliction.Prefab.HealableInMedicalClinic && affliction.Strength > GetShowTreshold(affliction);
277  static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
278  }
279 
280  private ImmutableArray<NetAffliction> GetAllAfflictions(CharacterHealth health)
281  {
282  IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(IsHealable);
283 
284  List<NetAffliction> afflictions = new List<NetAffliction>();
285 
286  foreach (Affliction affliction in rawAfflictions)
287  {
288  NetAffliction newAffliction;
289  if (afflictions.FirstOrNull(netAffliction => netAffliction.AfflictionEquals(affliction.Prefab)) is { } foundAffliction)
290  {
291  afflictions.Remove(foundAffliction);
292  foundAffliction.Strength += (ushort)affliction.Strength;
293  foundAffliction.Price += (ushort)GetAdjustedPrice(GetHealPrice(affliction));
294  newAffliction = foundAffliction;
295  }
296  else
297  {
298  newAffliction = new NetAffliction();
299  newAffliction.SetAffliction(affliction, health);
300  newAffliction.Price = (ushort)GetAdjustedPrice(newAffliction.Price);
301  }
302 
303  afflictions.Add(newAffliction);
304  }
305 
306  return afflictions.ToImmutableArray();
307 
308  static int GetHealPrice(Affliction affliction) => (int)(affliction.Prefab.BaseHealCost + (affliction.Prefab.HealCostMultiplier * affliction.Strength));
309  }
310 
311  public static void OnAfflictionCountChanged(Character character) =>
312  GameMain.GameSession?.Campaign?.MedicalClinic?.OnAfflictionCountChangedPrivate(character);
313 
314  private void OnAfflictionCountChangedPrivate(Character character)
315  {
316  if (character?.Info == null) { return; }
317  charactersWithAfflictionChanges.Add(character);
318  }
319 
320  public int GetTotalCost() => PendingHeals.SelectMany(static h => h.Afflictions).Aggregate(0, static (current, affliction) => current + affliction.Price);
321 
322  private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue;
323 
324  public int GetBalance() => campaign?.GetBalance() ?? 0;
325 
326  public static ImmutableArray<CharacterInfo> GetCrewCharacters()
327  {
328 #if DEBUG && CLIENT
329  if (Screen.Selected is TestScreen)
330  {
331  return TestInfos.ToImmutableArray();
332  }
333 #endif
334 
335  return Character.CharacterList.Where(static c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(static c => c.Info).ToImmutableArray();
336  }
337 
338 #if DEBUG && CLIENT
339  private static readonly CharacterInfo[] TestInfos =
340  {
341  new CharacterInfo(CharacterPrefab.HumanSpeciesName),
342  new CharacterInfo(CharacterPrefab.HumanSpeciesName),
343  new CharacterInfo(CharacterPrefab.HumanSpeciesName),
344  new CharacterInfo(CharacterPrefab.HumanSpeciesName),
345  new CharacterInfo(CharacterPrefab.HumanSpeciesName),
346  new CharacterInfo(CharacterPrefab.HumanSpeciesName),
347  new CharacterInfo(CharacterPrefab.HumanSpeciesName)
348  };
349 
350  private static readonly NetAffliction[] TestAfflictions =
351  {
352  new NetAffliction { Identifier = "internaldamage".ToIdentifier(), Strength = 80, Price = 10 },
353  new NetAffliction { Identifier = "blunttrauma".ToIdentifier(), Strength = 50, Price = 10 },
354  new NetAffliction { Identifier = "lacerations".ToIdentifier(), Strength = 20, Price = 10 },
355  new NetAffliction { Identifier = AfflictionPrefab.DamageType, Strength = 10, Price = 10 }
356  };
357 #endif
358  }
359 }
Identifier Identifier
Definition: Affliction.cs:62
float GetVitalityDecrease(CharacterHealth characterHealth)
Definition: Affliction.cs:171
virtual float Strength
Definition: Affliction.cs:31
readonly AfflictionPrefab Prefab
Definition: Affliction.cs:12
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
readonly int BaseHealCost
The minimum cost of healing this affliction at the medical clinic.
readonly float HealCostMultiplier
How much each unit of this affliction's strength will add to the cost of healing at the medical clini...
static IEnumerable< AfflictionPrefab > List
Stores information about the Character that is needed between rounds in the menu etc....
ushort ID
Unique ID given to character infos in MP. Non-persistent. Used by clients to identify which infos are...
int GetAdjustedHealCost(int cost)
Definition: Location.cs:1289
Marks fields and properties as to be serialized and deserialized by INetSerializableStruct....
readonly Identifier Identifier
Definition: Prefab.cs:34
void SetAffliction(Affliction affliction, CharacterHealth characterHealth)
readonly? CharacterInfo FindCharacterInfo(ImmutableArray< CharacterInfo > crew)
NetCrewMember(CharacterInfo info, ImmutableArray< NetAffliction > afflictions)