1 using System.Collections.Generic;
6 using Microsoft.Xna.Framework;
18 Initial, Dormant, Transition, Active, Final
21 private bool subscribedToDeathEvent;
25 private List<Limb> huskAppendage;
29 private bool stun =
false;
31 private readonly List<Affliction> huskInfection =
new List<Affliction>();
41 float threshold =
_strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
42 float max = Math.Max(threshold, previousValue);
45 if (previousValue > 0.0f && value <= 0.0f)
53 private float highestStrength;
60 if (state == value) {
return; }
68 private float ActiveThreshold => HuskPrefab.ActiveThreshold;
69 private float TransitionThreshold => HuskPrefab.TransitionThreshold;
71 private float TransformThresholdOnDeath => HuskPrefab.TransformThresholdOnDeath;
76 if (HuskPrefab ==
null)
78 DebugConsole.ThrowError(
"Error in husk affliction definition: the prefab is of wrong type!",
85 if (HuskPrefab ==
null) {
return; }
86 base.Update(characterHealth, targetLimb, deltaTime);
87 highestStrength = Math.Max(
_strength, highestStrength);
89 if (character ==
null) {
return; }
93 if (!subscribedToDeathEvent)
95 character.
OnDeath += CharacterDead;
96 subscribedToDeathEvent =
true;
101 if (
Strength > Math.Min(1.0f, DormantThreshold))
106 else if (
Strength < ActiveThreshold)
115 else if (
Strength < TransitionThreshold)
119 character.
SetStun(Rand.Range(2f, 3f));
132 ApplyDamage(deltaTime, applyForce:
true);
138 private void UpdateMessages()
141 if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value ==
State) {
return; }
142 if (highestStrength >
Strength) {
return; }
145 bool showHuskWarning = GameMain.GameSession?.Campaign?.Settings.ShowHuskWarning ??
true;
150 if (
Strength < DormantThreshold * 0.5f)
159 GUI.AddMessage(TextManager.Get(
"HuskDormant"), GUIStyle.Red);
162 else if (character.
IsBot)
164 character.
Speak(TextManager.Get(
"dialoghuskdormant").Value, delay: Rand.Range(0.5f, 5.0f), identifier:
"huskdormant".ToIdentifier());
172 GUI.AddMessage(TextManager.Get(
"HuskCantSpeak"), GUIStyle.Red);
175 else if (character.
IsBot)
177 character.
Speak(TextManager.Get(
"dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier:
"huskcantspeak".ToIdentifier());
184 GUI.AddMessage(TextManager.GetWithVariable(
"HuskActivate",
"[Attack]", GameSettings.CurrentConfig.KeyMap.KeyBindText(
InputType.Attack)), GUIStyle.Red);
192 prevDisplayedMessage =
State;
195 private void ApplyDamage(
float deltaTime,
bool applyForce)
197 int limbCount = character.
AnimController.
Limbs.Count(l => !l.IgnoreCollisions && !l.IsSevered && !l.Hidden);
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));
206 float force = applyForce ? random * 0.5f * limb.Mass : 0;
207 character.
DamageLimb(limb.WorldPosition, limb, huskInfection, 0,
false, Rand.Vector(force));
229 private void DeactivateHusk()
232 if (
Prefab is AfflictionPrefabHusk { NeedsAir:
false } &&
238 if (huskAppendage !=
null)
241 huskAppendage =
null;
247 if (character ==
null || !subscribedToDeathEvent) {
return; }
248 character.
OnDeath -= CharacterDead;
249 subscribedToDeathEvent =
false;
267 if (limbJoint.IsSevered) {
return; }
272 CoroutineManager.StartCoroutine(CreateAIHusk());
275 private IEnumerable<CoroutineStatus> CreateAIHusk()
279 if (Entity.Spawner.IsInRemoveQueue(character))
281 yield
return CoroutineStatus.Success;
284 var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
287 Entity.Spawner.AddEntityToRemoveQueue(character);
291 CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
295 DebugConsole.ThrowError(
"Failed to turn character \"" + character.
Name +
"\" into a husk - husk config file not found.",
297 yield
return CoroutineStatus.Success;
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));
304 if (huskCharacterInfo !=
null)
307 huskCharacterInfo.Head.SkinColor =
308 Color.Lerp(huskCharacterInfo.Head.SkinColor, bodyTint.Opaque(), bodyTint.A / 255.0f);
311 var husk =
Character.Create(huskedSpeciesName, character.
WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer:
false, hasAi:
true);
312 if (husk.Info !=
null)
314 husk.Info.Character = husk;
318 if (
Prefab is AfflictionPrefabHusk huskPrefab)
320 if (huskPrefab.ControlHusk || GameMain.LuaCs.Game.enableControlHusk)
325 GameMain.Server.SetClientCharacter(client, husk);
326 GameMain.LuaCs.Hook.Call(
"husk.clientControlHusk",
new object[] { client, husk });
337 foreach (Limb limb
in husk.AnimController.Limbs)
341 limb.body.SetTransform(character.
SimPosition, 0.0f);
346 if (matchingLimb?.body !=
null)
349 limb.body.LinearVelocity = matchingLimb.LinearVelocity;
350 limb.body.AngularVelocity = matchingLimb.body.AngularVelocity;
354 if ((
Prefab as AfflictionPrefabHusk)?.TransferBuffs ??
false)
358 if (affliction.Prefab.IsBuff)
360 husk.CharacterHealth.ApplyAffliction(
362 affliction.Prefab.Instantiate(affliction.Strength));
369 for (
int i = 0; i < character.
Inventory.
Capacity && i < husk.Inventory.Capacity; i++)
371 character.
Inventory.
GetItemsAt(i).ForEachMod(item => husk.Inventory.TryPutItem(item, i,
true,
false,
null));
376 yield
return new WaitForSeconds(5,
false);
378 husk?.PlaySound(CharacterSound.SoundType.Idle);
380 yield
return CoroutineStatus.Success;
385 var appendage =
new List<Limb>();
389 if (huskPrefab?.ConfigElement ==
null)
391 DebugConsole.ThrowError($
"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!",
396 var element = appendageDefinition;
403 DebugConsole.ThrowError($
"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{matchingAffliction.Identifier}'!",
408 XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage);
409 if (doc ==
null) {
return appendage; }
414 if (ragdoll.Dir < 1.0f)
420 var limbElements = root.GetChildElements(
"limb").ToDictionary(e => e.GetAttributeString(
"id",
null), e => e);
424 foreach (var jointElement
in root.GetChildElements(
"joint"))
426 if (!limbElements.TryGetValue(jointElement.GetAttributeString(
"limb2",
null), out
ContentXElement limbElement)) {
continue; }
429 Limb attachLimb =
null;
430 if (matchingAffliction.AttachLimbId > -1)
432 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
434 else if (matchingAffliction.AttachLimbName !=
null)
436 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
438 else if (matchingAffliction.AttachLimbType !=
LimbType.None)
440 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
442 if (attachLimb ==
null)
444 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
446 if (attachLimb !=
null)
448 jointParams.Limb1 = attachLimb.
Params.ID;
451 if (jointParams.Limb1 >= ragdoll.RagdollParams.Limbs.Count)
453 jointParams.Limb1 += idOffset;
458 idOffset = ragdoll.Limbs.Length - appendageLimbParams.
ID;
460 jointParams.Limb2 = appendageLimbParams.ID = ragdoll.Limbs.Length;
461 Limb huskAppendage =
new Limb(ragdoll, character, appendageLimbParams);
464 ragdoll.AddLimb(huskAppendage);
465 ragdoll.AddJoint(jointParams);
466 appendage.Add(huskAppendage);
void Serialize(XElement element)
Affliction(AfflictionPrefab prefab, float strength)
readonly AfflictionPrefab Prefab
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
void UnsubscribeFromDeathEvent()
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.
readonly Character Character
Limb GetAfflictionLimb(Affliction affliction)
IReadOnlyCollection< Affliction > GetAllAfflictions()
readonly CharacterParams Params
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
CharacterHealth CharacterHealth
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)
override Vector2? SimPosition
CharacterInventory Inventory
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)
readonly AnimController AnimController
XElement Save(XElement parentElement)
override ContentXElement? MainElement
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
readonly? ContentPackage ContentPackage
static readonly ContentPath Empty
IEnumerable< ContentXElement > GetChildElements(string name)
bool GetAttributeBool(string key, bool def)
virtual Vector2 WorldPosition
static GameSession?? GameSession
static NetworkMember NetworkMember
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...
readonly LimbParams Params
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
ContentPackage? ContentPackage
readonly Identifier Identifier
void RemoveLimb(Limb limb)
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.