4 using System.Collections.Generic;
5 using System.Collections.Immutable;
12 internal sealed
partial class MedicalClinic
14 private const int RateLimitMaxRequests = 20,
17 public enum NetworkHeader
27 ADD_EVERYTHING_TO_PENDING
30 public enum AfflictionSeverity
37 public enum MessageFlag
43 public enum HealRequestResult
52 public readonly record
struct NetHealRequest(HealRequestResult Result) : INetSerializableStruct;
55 public readonly record struct NetRemovedAffliction(NetCrewMember CrewMember, NetAffliction Affliction) : INetSerializableStruct;
74 Strength = (ushort)Math.Ceiling(affliction.
Strength);
85 if (cachedPrefab is { } cached) {
return cached; }
91 cachedPrefab = prefab;
100 cachedPrefab = value;
101 Identifier = value?.
Identifier ?? Identifier.Empty;
156 public readonly List<NetCrewMember> PendingHeals =
new List<NetCrewMember>();
158 public Action? OnUpdate;
165 private readonly HashSet<Character> charactersWithAfflictionChanges =
new HashSet<Character>();
167 private float processAfflictionChangesTimer;
168 private const float ProcessAfflictionChangesInterval = 1.0f;
172 this.campaign = campaign;
174 campaign.
OnMoneyChanged.RegisterOverwriteExisting(nameof(MedicalClinic).ToIdentifier(), OnMoneyChanged);
178 private static bool IsOutpostInCombat()
180 if (Level.Loaded is not { Type: LevelData.LevelType.Outpost }) {
return false; }
182 IEnumerable<Character> crew = GetCrewCharacters().Where(
static c => c.Character !=
null).Select(
static c => c.Character).ToImmutableHashSet();
186 bool isInCombatWithCrew = !npc.IsInstigator && npc.AIController is HumanAIController { ObjectiveManager: { CurrentObjective: AIObjectiveCombat combatObjective } } && crew.Contains(combatObjective.Enemy);
187 if (isInCombatWithCrew) {
return true; }
193 private HealRequestResult HealAllPending(
bool force =
false,
Client? client =
null)
195 int totalCost = GetTotalCost();
198 if (IsOutpostInCombat()) {
return HealRequestResult.Refused; }
199 if (!(campaign?.TryPurchase(client, totalCost) ??
false)) {
return HealRequestResult.InsufficientFunds; }
202 ImmutableArray<CharacterInfo> crew = GetCrewCharacters();
203 foreach (NetCrewMember crewMember
in PendingHeals)
205 CharacterInfo? targetCharacter = crewMember.FindCharacterInfo(crew);
206 if (!(targetCharacter?.Character is { CharacterHealth: { } health })) {
continue; }
208 foreach (NetAffliction affliction
in crewMember.Afflictions)
210 health.ReduceAfflictionOnAllLimbs(affliction.Identifier, affliction.Prefab?.MaxStrength ?? affliction.Strength);
216 return HealRequestResult.Success;
219 private void ClearPendingHeals()
221 PendingHeals.Clear();
224 private void AddEverythingToPending()
226 foreach (CharacterInfo info
in GetCrewCharacters())
228 if (info.Character?.CharacterHealth is not { } health) {
continue; }
230 var afflictions = GetAllAfflictions(health);
232 if (afflictions.Length is 0) {
continue; }
234 InsertPendingCrewMember(
new NetCrewMember(info, afflictions));
238 private void RemovePendingAffliction(NetCrewMember crewMember, NetAffliction affliction)
240 foreach (NetCrewMember listMember
in PendingHeals.ToList())
242 PendingHeals.Remove(listMember);
243 NetCrewMember pendingMember = listMember;
245 if (pendingMember.CharacterEquals(crewMember))
247 List<NetAffliction> newAfflictions =
new List<NetAffliction>();
248 foreach (NetAffliction pendingAffliction
in pendingMember.Afflictions)
250 if (pendingAffliction.AfflictionEquals(affliction)) {
continue; }
252 newAfflictions.Add(pendingAffliction);
255 pendingMember.Afflictions = newAfflictions.ToImmutableArray();
258 if (!pendingMember.Afflictions.Any()) {
continue; }
260 PendingHeals.Add(pendingMember);
264 private void InsertPendingCrewMember(NetCrewMember crewMember)
266 if (PendingHeals.FirstOrNull(m => m.CharacterEquals(crewMember)) is { } foundHeal)
268 PendingHeals.Remove(foundHeal);
271 PendingHeals.Add(crewMember);
274 public static bool IsHealable(Affliction affliction)
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));
280 private ImmutableArray<NetAffliction> GetAllAfflictions(CharacterHealth health)
282 IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(IsHealable);
284 List<NetAffliction> afflictions =
new List<NetAffliction>();
286 foreach (Affliction affliction
in rawAfflictions)
288 NetAffliction newAffliction;
289 if (afflictions.FirstOrNull(netAffliction => netAffliction.AfflictionEquals(affliction.Prefab)) is { } foundAffliction)
291 afflictions.Remove(foundAffliction);
292 foundAffliction.Strength += (ushort)affliction.Strength;
293 foundAffliction.Price += (ushort)GetAdjustedPrice(GetHealPrice(affliction));
294 newAffliction = foundAffliction;
298 newAffliction =
new NetAffliction();
299 newAffliction.SetAffliction(affliction, health);
300 newAffliction.Price = (ushort)GetAdjustedPrice(newAffliction.Price);
303 afflictions.Add(newAffliction);
306 return afflictions.ToImmutableArray();
308 static int GetHealPrice(Affliction affliction) => (int)(affliction.Prefab.BaseHealCost + (affliction.Prefab.HealCostMultiplier * affliction.Strength));
311 public static void OnAfflictionCountChanged(Character character) =>
312 GameMain.GameSession?.Campaign?.MedicalClinic?.OnAfflictionCountChangedPrivate(character);
314 private void OnAfflictionCountChangedPrivate(Character character)
316 if (character?.Info ==
null) {
return; }
317 charactersWithAfflictionChanges.Add(character);
320 public int GetTotalCost() => PendingHeals.SelectMany(
static h => h.Afflictions).Aggregate(0,
static (current, affliction) => current + affliction.Price);
322 private int GetAdjustedPrice(
int price) => campaign?.
Map?.
CurrentLocation is { Type: { HasOutpost:
true } } currentLocation ? currentLocation.
GetAdjustedHealCost(price) :
int.MaxValue;
324 public int GetBalance() => campaign?.
GetBalance() ?? 0;
326 public static ImmutableArray<CharacterInfo> GetCrewCharacters()
329 if (Screen.Selected is TestScreen)
331 return TestInfos.ToImmutableArray();
335 return Character.CharacterList.Where(
static c => c.Info !=
null && c.TeamID ==
CharacterTeamType.Team1).Select(
static c => c.Info).ToImmutableArray();
339 private static readonly CharacterInfo[] TestInfos =
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)
350 private static readonly NetAffliction[] TestAfflictions =
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 }
float GetVitalityDecrease(CharacterHealth characterHealth)
readonly AfflictionPrefab Prefab
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
virtual int GetBalance(Client client=null)
readonly NamedEvent< WalletChangedEvent > OnMoneyChanged
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)
Marks fields and properties as to be serialized and deserialized by INetSerializableStruct....
readonly Identifier Identifier
readonly bool AfflictionEquals(NetAffliction affliction)
void SetAffliction(Affliction affliction, CharacterHealth characterHealth)
readonly bool AfflictionEquals(AfflictionPrefab prefab)
readonly bool CharacterEquals(NetCrewMember crewMember)
readonly? CharacterInfo FindCharacterInfo(ImmutableArray< CharacterInfo > crew)
NetCrewMember(CharacterInfo info)
ImmutableArray< NetAffliction > Afflictions
NetCrewMember(CharacterInfo info, ImmutableArray< NetAffliction > afflictions)