Client LuaCsForBarotrauma
AfflictionHusk.cs
1 using System.Collections.Generic;
2 using System.Linq;
3 using System.Xml.Linq;
4 using System;
6 using Microsoft.Xna.Framework;
7 
8 namespace Barotrauma
9 {
14  partial class AfflictionHusk : Affliction
15  {
16  public enum InfectionState
17  {
18  Initial, Dormant, Transition, Active, Final
19  }
20 
21  private bool subscribedToDeathEvent;
22 
23  private InfectionState state;
24 
25  private List<Limb> huskAppendage;
26 
27  private Character character;
28 
29  private bool stun = false;
30 
31  private readonly List<Affliction> huskInfection = new List<Affliction>();
32 
34  public override float Strength
35  {
36  get { return _strength; }
37  set
38  {
39  // Don't allow to set the strength too high (from outside) to avoid rapid transformation into husk when taking lots of damage from husks.
40  float previousValue = _strength;
41  float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
42  float max = Math.Max(threshold, previousValue);
43  _strength = Math.Clamp(value, 0, max);
44  stun = GameMain.GameSession?.IsRunning ?? true;
45  if (previousValue > 0.0f && value <= 0.0f)
46  {
47  DeactivateHusk();
48  highestStrength = 0;
49  }
50  activeEffectDirty = true;
51  }
52  }
53  private float highestStrength;
54 
56  {
57  get { return state; }
58  private set
59  {
60  if (state == value) { return; }
61  state = value;
62  }
63  }
64 
65  private readonly AfflictionPrefabHusk HuskPrefab;
66 
67  private float DormantThreshold => HuskPrefab.DormantThreshold;
68  private float ActiveThreshold => HuskPrefab.ActiveThreshold;
69  private float TransitionThreshold => HuskPrefab.TransitionThreshold;
70 
71  private float TransformThresholdOnDeath => HuskPrefab.TransformThresholdOnDeath;
72 
73  public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength)
74  {
75  HuskPrefab = prefab as AfflictionPrefabHusk;
76  if (HuskPrefab == null)
77  {
78  DebugConsole.ThrowError("Error in husk affliction definition: the prefab is of wrong type!",
79  contentPackage: prefab.ContentPackage);
80  }
81  }
82 
83  public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
84  {
85  if (HuskPrefab == null) { return; }
86  base.Update(characterHealth, targetLimb, deltaTime);
87  highestStrength = Math.Max(_strength, highestStrength);
88  character = characterHealth.Character;
89  if (character == null) { return; }
90 
91  UpdateMessages();
92 
93  if (!subscribedToDeathEvent)
94  {
95  character.OnDeath += CharacterDead;
96  subscribedToDeathEvent = true;
97  }
98  if (Strength < DormantThreshold)
99  {
100  DeactivateHusk();
101  if (Strength > Math.Min(1.0f, DormantThreshold))
102  {
103  State = InfectionState.Dormant;
104  }
105  }
106  else if (Strength < ActiveThreshold)
107  {
108  DeactivateHusk();
109  if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
110  {
111  character.SpeechImpediment = 30;
112  }
113  State = InfectionState.Transition;
114  }
115  else if (Strength < TransitionThreshold)
116  {
117  if (State != InfectionState.Active && stun)
118  {
119  character.SetStun(Rand.Range(2f, 3f));
120  }
121  if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
122  {
123  character.SpeechImpediment = 100;
124  }
125  State = InfectionState.Active;
126  ActivateHusk();
127  }
128  else
129  {
130  State = InfectionState.Final;
131  ActivateHusk();
132  ApplyDamage(deltaTime, applyForce: true);
133  character.SetStun(5);
134  }
135  }
136 
137  private InfectionState? prevDisplayedMessage;
138  private void UpdateMessages()
139  {
140  if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; }
141  if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; }
142  if (highestStrength > Strength) { return; }
143 
144  // Show initial husk warning by default, and disable it only if campaign difficulty settings explicitly disable it
145  bool showHuskWarning = GameMain.GameSession?.Campaign?.Settings.ShowHuskWarning ?? true;
146 
147  switch (State)
148  {
149  case InfectionState.Dormant:
150  if (Strength < DormantThreshold * 0.5f)
151  {
152  return;
153  }
154  if (showHuskWarning)
155  {
156  if (character == Character.Controlled)
157  {
158 #if CLIENT
159  GUI.AddMessage(TextManager.Get("HuskDormant"), GUIStyle.Red);
160 #endif
161  }
162  else if (character.IsBot)
163  {
164  character.Speak(TextManager.Get("dialoghuskdormant").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskdormant".ToIdentifier());
165  }
166  }
167  break;
168  case InfectionState.Transition:
169  if (character == Character.Controlled)
170  {
171 #if CLIENT
172  GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
173 #endif
174  }
175  else if (character.IsBot)
176  {
177  character.Speak(TextManager.Get("dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskcantspeak".ToIdentifier());
178  }
179  break;
180  case InfectionState.Active:
181 #if CLIENT
182  if (character == Character.Controlled && character.Params.UseHuskAppendage)
183  {
184  GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Attack)), GUIStyle.Red);
185  }
186 #endif
187  break;
188  case InfectionState.Final:
189  default:
190  break;
191  }
192  prevDisplayedMessage = State;
193  }
194 
195  private void ApplyDamage(float deltaTime, bool applyForce)
196  {
197  int limbCount = character.AnimController.Limbs.Count(l => !l.IgnoreCollisions && !l.IsSevered && !l.Hidden);
198  foreach (Limb limb in character.AnimController.Limbs)
199  {
200  if (limb.IsSevered) { continue; }
201  if (limb.Hidden) { continue; }
202  float random = Rand.Value();
203  huskInfection.Clear();
204  huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / limbCount));
205  character.LastDamageSource = null;
206  float force = applyForce ? random * 0.5f * limb.Mass : 0;
207  character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, Rand.Vector(force));
208  }
209  }
210 
211  public void ActivateHusk()
212  {
213  if (huskAppendage == null && character.Params.UseHuskAppendage)
214  {
215  huskAppendage = AttachHuskAppendage(character, Prefab as AfflictionPrefabHusk);
216  }
217 
218  if (Prefab is AfflictionPrefabHusk { NeedsAir: false })
219  {
220  character.NeedsAir = false;
221  }
222 
223  if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
224  {
225  character.SpeechImpediment = 100;
226  }
227  }
228 
229  private void DeactivateHusk()
230  {
231  if (character?.AnimController == null || character.Removed) { return; }
232  if (Prefab is AfflictionPrefabHusk { NeedsAir: false } &&
233  !character.CharacterHealth.GetAllAfflictions().Any(a => a != this && a.Prefab is AfflictionPrefabHusk { NeedsAir: false }))
234  {
235  character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
236  }
237 
238  if (huskAppendage != null)
239  {
240  huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
241  huskAppendage = null;
242  }
243  }
244 
246  {
247  if (character == null || !subscribedToDeathEvent) { return; }
248  character.OnDeath -= CharacterDead;
249  subscribedToDeathEvent = false;
250  }
251 
252  private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
253  {
254  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
255  if (Strength < TransformThresholdOnDeath || character.Removed ||
256  character.CharacterHealth.GetAllAfflictions().Any(a => a.GetActiveEffect()?.BlockTransformation.Contains(Prefab.Identifier) ?? false))
257  {
259  return;
260  }
261 
262  //don't turn the character into a husk if any of its limbs are severed
263  if (character.AnimController?.LimbJoints != null)
264  {
265  foreach (var limbJoint in character.AnimController.LimbJoints)
266  {
267  if (limbJoint.IsSevered) { return; }
268  }
269  }
270 
271  //create the AI husk in a coroutine to ensure that we don't modify the character list while enumerating it
272  CoroutineManager.StartCoroutine(CreateAIHusk());
273  }
274 
275  private IEnumerable<CoroutineStatus> CreateAIHusk()
276  {
277  //character already in remove queue (being removed by something else, for example a modded affliction that uses AfflictionHusk as the base)
278  // -> don't spawn the AI husk
279  if (Entity.Spawner.IsInRemoveQueue(character))
280  {
281  yield return CoroutineStatus.Success;
282  }
283 #if SERVER
284  var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
285 #endif
286  character.Enabled = false;
287  Entity.Spawner.AddEntityToRemoveQueue(character);
289 
290  Identifier huskedSpeciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
291  CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
292 
293  if (prefab == null)
294  {
295  DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.",
296  contentPackage: Prefab.ContentPackage);
297  yield return CoroutineStatus.Success;
298  }
299 
300  XElement parentElement = new XElement("CharacterInfo");
301  XElement infoElement = character.Info?.Save(parentElement);
302  CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(new ContentXElement(Prefab.ContentPackage, infoElement));
303 
304  if (huskCharacterInfo != null)
305  {
306  var bodyTint = GetBodyTint();
307  huskCharacterInfo.Head.SkinColor =
308  Color.Lerp(huskCharacterInfo.Head.SkinColor, bodyTint.Opaque(), bodyTint.A / 255.0f);
309  }
310 
311  var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer: false, hasAi: true);
312  if (husk.Info != null)
313  {
314  husk.Info.Character = husk;
315  husk.Info.TeamID = CharacterTeamType.None;
316  }
317 
318  if (Prefab is AfflictionPrefabHusk huskPrefab)
319  {
320  if (huskPrefab.ControlHusk || GameMain.LuaCs.Game.enableControlHusk)
321  {
322 #if SERVER
323  if (client != null)
324  {
325  GameMain.Server.SetClientCharacter(client, husk);
326  GameMain.LuaCs.Hook.Call("husk.clientControlHusk", new object[] { client, husk });
327  }
328 #else
329  if (!character.IsRemotelyControlled && character == Character.Controlled)
330  {
331  Character.Controlled = husk;
332  }
333 #endif
334  }
335  }
336 
337  foreach (Limb limb in husk.AnimController.Limbs)
338  {
339  if (limb.type == LimbType.None)
340  {
341  limb.body.SetTransform(character.SimPosition, 0.0f);
342  continue;
343  }
344 
345  var matchingLimb = character.AnimController.GetLimb(limb.type);
346  if (matchingLimb?.body != null)
347  {
348  limb.body.SetTransform(matchingLimb.SimPosition, matchingLimb.Rotation);
349  limb.body.LinearVelocity = matchingLimb.LinearVelocity;
350  limb.body.AngularVelocity = matchingLimb.body.AngularVelocity;
351  }
352  }
353 
354  if ((Prefab as AfflictionPrefabHusk)?.TransferBuffs ?? false)
355  {
356  foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
357  {
358  if (affliction.Prefab.IsBuff)
359  {
360  husk.CharacterHealth.ApplyAffliction(
361  character.CharacterHealth.GetAfflictionLimb(affliction),
362  affliction.Prefab.Instantiate(affliction.Strength));
363  }
364  }
365  }
366 
367  if (character.Inventory != null && husk.Inventory != null)
368  {
369  for (int i = 0; i < character.Inventory.Capacity && i < husk.Inventory.Capacity; i++)
370  {
371  character.Inventory.GetItemsAt(i).ForEachMod(item => husk.Inventory.TryPutItem(item, i, true, false, null));
372  }
373  }
374 
375  husk.SetStun(5);
376  yield return new WaitForSeconds(5, false);
377 #if CLIENT
378  husk?.PlaySound(CharacterSound.SoundType.Idle);
379 #endif
380  yield return CoroutineStatus.Success;
381  }
382 
383  public static List<Limb> AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, ContentXElement appendageDefinition = null, Ragdoll ragdoll = null)
384  {
385  var appendage = new List<Limb>();
386  Identifier nonhuskedSpeciesName = GetNonHuskedSpeciesName(character.SpeciesName, matchingAffliction);
387  Identifier huskedSpeciesName = GetHuskedSpeciesName(nonhuskedSpeciesName, matchingAffliction);
388  CharacterPrefab huskPrefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
389  if (huskPrefab?.ConfigElement == null)
390  {
391  DebugConsole.ThrowError($"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!",
392  contentPackage: matchingAffliction.ContentPackage);
393  return appendage;
394  }
395  var mainElement = huskPrefab.ConfigElement;
396  var element = appendageDefinition;
397  if (element == null)
398  {
399  element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeIdentifier("affliction", Identifier.Empty) == matchingAffliction.Identifier);
400  }
401  if (element == null)
402  {
403  DebugConsole.ThrowError($"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{matchingAffliction.Identifier}'!",
404  contentPackage: matchingAffliction.ContentPackage);
405  return appendage;
406  }
407  ContentPath pathToAppendage = element.GetAttributeContentPath("path") ?? ContentPath.Empty;
408  XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage);
409  if (doc == null) { return appendage; }
410  if (ragdoll == null)
411  {
412  ragdoll = character.AnimController;
413  }
414  if (ragdoll.Dir < 1.0f)
415  {
416  ragdoll.Flip();
417  }
418 
419  var root = doc.Root.FromPackage(pathToAppendage.ContentPackage);
420  var limbElements = root.GetChildElements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);
421  //the IDs may need to be offset if the character has other extra appendages (e.g. from gene splicing)
422  //that take up the IDs of this appendage
423  int idOffset = 0;
424  foreach (var jointElement in root.GetChildElements("joint"))
425  {
426  if (!limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement)) { continue; }
427 
428  var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
429  Limb attachLimb = null;
430  if (matchingAffliction.AttachLimbId > -1)
431  {
432  attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
433  }
434  else if (matchingAffliction.AttachLimbName != null)
435  {
436  attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
437  }
438  else if (matchingAffliction.AttachLimbType != LimbType.None)
439  {
440  attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
441  }
442  if (attachLimb == null)
443  {
444  attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
445  }
446  if (attachLimb != null)
447  {
448  jointParams.Limb1 = attachLimb.Params.ID;
449  //the joint attaches to a limb outside the character's normal limb count = to another part of the appendage
450  // -> if the appendage's IDs have been offset, we need to take that into account to attach to the correct limb
451  if (jointParams.Limb1 >= ragdoll.RagdollParams.Limbs.Count)
452  {
453  jointParams.Limb1 += idOffset;
454  }
455  var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams);
456  if (idOffset == 0)
457  {
458  idOffset = ragdoll.Limbs.Length - appendageLimbParams.ID;
459  }
460  jointParams.Limb2 = appendageLimbParams.ID = ragdoll.Limbs.Length;
461  Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
462  huskAppendage.body.Submarine = character.Submarine;
463  huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
464  ragdoll.AddLimb(huskAppendage);
465  ragdoll.AddJoint(jointParams);
466  appendage.Add(huskAppendage);
467  }
468  }
469  return appendage;
470  }
471 
473  {
474  return new Identifier(speciesName.Value + prefab.HuskedSpeciesName.Value);
475  }
476 
477  public static Identifier GetNonHuskedSpeciesName(Identifier huskedSpeciesName, AfflictionPrefabHusk prefab)
478  {
479  return huskedSpeciesName.Remove(prefab.HuskedSpeciesName);
480  }
481  }
482 }
Identifier Identifier
Definition: Affliction.cs:62
void Serialize(XElement element)
Definition: Affliction.cs:125
Affliction(AfflictionPrefab prefab, float strength)
Definition: Affliction.cs:97
readonly AfflictionPrefab Prefab
Definition: Affliction.cs:12
A special affliction type that gradually makes the character turn into another type of character....
static List< Limb > AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, ContentXElement appendageDefinition=null, Ragdoll ragdoll=null)
override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
static Identifier GetHuskedSpeciesName(Identifier speciesName, AfflictionPrefabHusk prefab)
AfflictionHusk(AfflictionPrefab prefab, float strength)
static Identifier GetNonHuskedSpeciesName(Identifier huskedSpeciesName, AfflictionPrefabHusk prefab)
override float???? Strength
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
AfflictionPrefabHusk is a special type of affliction that has added functionality for husk infection.
readonly float DormantThreshold
The minimum strength at which husk infection will be in the dormant stage. It must be less than or eq...
readonly Identifier HuskedSpeciesName
The species of husk to convert the affected character to once husk infection reaches its final stage.
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
void SetStun(float newStun, bool allowStunDecrease=false, bool isNetworkMessage=false)
AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable< Affliction > afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker=null, float damageMultiplier=1, bool allowStacking=true, float penetration=0f, bool shouldImplode=false)
override ContentXElement? MainElement
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
readonly? ContentPackage ContentPackage
Definition: ContentPath.cs:21
static readonly ContentPath Empty
Definition: ContentPath.cs:12
IEnumerable< ContentXElement > GetChildElements(string name)
bool GetAttributeBool(string key, bool def)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
static GameSession?? GameSession
Definition: GameMain.cs:88
static NetworkMember NetworkMember
Definition: GameMain.cs:190
Inventory(Entity owner, int capacity, int slotsPerRow=5)
IEnumerable< Item > GetItemsAt(int index)
Get all the item stored in the specified inventory slot. Can return more than one item if the slot co...
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.