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;
39 float threshold =
_strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
40 float max = Math.Max(threshold, previousValue);
43 if (previousValue > 0.0f && value <= 0.0f)
51 private float highestStrength;
58 if (state == value) {
return; }
76 DebugConsole.ThrowError(
"Error in husk affliction definition: the prefab is of wrong type!",
84 base.Update(characterHealth, targetLimb, deltaTime);
85 highestStrength = Math.Max(
_strength, highestStrength);
87 if (character ==
null) {
return; }
91 if (!subscribedToDeathEvent)
93 character.
OnDeath += CharacterDead;
94 subscribedToDeathEvent =
true;
99 if (
Strength > Math.Min(1.0f, DormantThreshold))
104 else if (
Strength < ActiveThreshold)
113 else if (
Strength < TransitionThreshold)
117 character.
SetStun(Rand.Range(2f, 3f));
130 ApplyDamage(deltaTime);
136 private void UpdateMessages()
139 if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value ==
State) {
return; }
140 if (highestStrength >
Strength) {
return; }
143 bool showHuskWarning = GameMain.GameSession?.Campaign?.Settings.ShowHuskWarning ??
true;
148 if (
Strength < DormantThreshold * 0.5f)
157 GUI.AddMessage(TextManager.Get(
"HuskDormant"), GUIStyle.Red);
160 else if (character.
IsBot)
162 character.
Speak(TextManager.Get(
"dialoghuskdormant").Value, delay: Rand.Range(0.5f, 5.0f), identifier:
"huskdormant".ToIdentifier());
170 GUI.AddMessage(TextManager.Get(
"HuskCantSpeak"), GUIStyle.Red);
173 else if (character.
IsBot)
175 character.
Speak(TextManager.Get(
"dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier:
"huskcantspeak".ToIdentifier());
182 GUI.AddMessage(TextManager.GetWithVariable(
"HuskActivate",
"[Attack]", GameSettings.CurrentConfig.KeyMap.KeyBindText(
InputType.Attack)), GUIStyle.Red);
190 prevDisplayedMessage =
State;
193 private const float DamageCooldown = 0.1f;
194 private float damageCooldownTimer;
195 private void ApplyDamage(
float deltaTime)
197 if (damageCooldownTimer > 0)
199 damageCooldownTimer -= deltaTime;
202 damageCooldownTimer = DamageCooldown;
206 if (!IsValidLimb(limb)) {
continue; }
207 float random = Rand.Value();
208 if (random == 0) {
continue; }
209 const float damageRate = 2;
210 float dmg = random / limbCount * damageRate;
212 var afflictions = AfflictionPrefab.InternalDamage.Instantiate(dmg).ToEnumerable();
213 const float forceMultiplier = 5;
214 float force = dmg * limb.Mass * forceMultiplier;
215 character.
DamageLimb(limb.WorldPosition, limb, afflictions, stun: 0, playSound:
false, Rand.Vector(force), ignoreDamageOverlay:
true, recalculateVitality:
false);
219 static bool IsValidLimb(Limb limb) => !limb.IgnoreCollisions && !limb.IsSevered && !limb.Hidden;
241 private void DeactivateHusk()
244 if (
Prefab is AfflictionPrefabHusk { NeedsAir:
false } &&
250 if (huskAppendage !=
null)
253 huskAppendage =
null;
259 if (character ==
null || !subscribedToDeathEvent) {
return; }
260 character.
OnDeath -= CharacterDead;
261 subscribedToDeathEvent =
false;
279 if (limbJoint.IsSevered) {
return; }
284 CoroutineManager.StartCoroutine(CreateAIHusk());
287 private IEnumerable<CoroutineStatus> CreateAIHusk()
291 if (Entity.Spawner.IsInRemoveQueue(character))
293 yield
return CoroutineStatus.Success;
296 var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
299 Entity.Spawner.AddEntityToRemoveQueue(character);
303 CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
307 DebugConsole.ThrowError(
"Failed to turn character \"" + character.
Name +
"\" into a husk - husk config file not found.",
309 yield
return CoroutineStatus.Success;
312 XElement parentElement =
new XElement(
"CharacterInfo");
313 XElement infoElement = character.
Info?.
Save(parentElement);
314 CharacterInfo huskCharacterInfo = infoElement ==
null ? null :
new CharacterInfo(
new ContentXElement(
Prefab.
ContentPackage, infoElement));
316 if (huskCharacterInfo !=
null)
319 huskCharacterInfo.Head.SkinColor =
320 Color.Lerp(huskCharacterInfo.Head.SkinColor, bodyTint.Opaque(), bodyTint.A / 255.0f);
323 var husk =
Character.Create(huskedSpeciesName, character.
WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer:
false, hasAi:
true);
324 if (husk.Info !=
null)
326 husk.Info.Character = husk;
331 if (
Prefab is AfflictionPrefabHusk huskPrefab)
333 if (huskPrefab.ControlHusk || GameMain.LuaCs.Game.enableControlHusk)
338 GameMain.Server.SetClientCharacter(client, husk);
339 GameMain.LuaCs.Hook.Call(
"husk.clientControlHusk",
new object[] { client, husk });
350 foreach (Limb limb
in husk.AnimController.Limbs)
354 limb.body.SetTransform(character.
SimPosition, 0.0f);
359 if (matchingLimb?.body !=
null)
362 limb.body.LinearVelocity = matchingLimb.LinearVelocity;
363 limb.body.AngularVelocity = matchingLimb.body.AngularVelocity;
367 if ((
Prefab as AfflictionPrefabHusk)?.TransferBuffs ??
false)
371 if (affliction.Prefab.IsBuff)
373 husk.CharacterHealth.ApplyAffliction(
375 affliction.Prefab.Instantiate(affliction.Strength));
382 for (
int i = 0; i < character.
Inventory.
Capacity && i < husk.Inventory.Capacity; i++)
384 character.
Inventory.
GetItemsAt(i).ForEachMod(item => husk.Inventory.TryPutItem(item, i,
true,
false,
null));
389 yield
return new WaitForSeconds(5,
false);
391 husk?.PlaySound(CharacterSound.SoundType.Idle);
393 yield
return CoroutineStatus.Success;
398 var appendage =
new List<Limb>();
400 if (huskPrefab?.ConfigElement ==
null)
402 DebugConsole.ThrowError($
"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!",
407 var element = appendageDefinition;
414 DebugConsole.ThrowError($
"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{matchingAffliction.Identifier}'!",
419 XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage);
420 if (doc ==
null) {
return appendage; }
422 if (ragdoll.Dir < 1.0f)
428 var limbElements = root.GetChildElements(
"limb").ToDictionary(e => e.GetAttributeString(
"id",
null), e => e);
432 foreach (var jointElement
in root.GetChildElements(
"joint"))
434 if (!limbElements.TryGetValue(jointElement.GetAttributeString(
"limb2",
null), out
ContentXElement limbElement)) {
continue; }
437 Limb attachLimb =
null;
438 if (matchingAffliction.AttachLimbId > -1)
440 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
442 else if (matchingAffliction.AttachLimbName !=
null)
444 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
446 else if (matchingAffliction.AttachLimbType !=
LimbType.None)
448 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
450 if (attachLimb ==
null)
452 attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
454 if (attachLimb !=
null)
456 jointParams.Limb1 = attachLimb.
Params.ID;
459 if (jointParams.Limb1 >= ragdoll.RagdollParams.Limbs.Count)
461 jointParams.Limb1 += idOffset;
466 idOffset = ragdoll.Limbs.Length - appendageLimbParams.
ID;
468 jointParams.Limb2 = appendageLimbParams.ID = ragdoll.Limbs.Length;
469 Limb huskAppendage =
new Limb(ragdoll, character, appendageLimbParams);
472 ragdoll.AddLimb(huskAppendage);
473 ragdoll.AddJoint(jointParams);
474 appendage.Add(huskAppendage);
483 if (huskedSpecies.IsEmpty)
488 return huskedSpecies;
494 if (nonHuskedSpecies.IsEmpty)
499 return nonHuskedSpecies;
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....
readonly AfflictionPrefabHusk HuskPrefab
static List< Limb > AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, Identifier huskedSpeciesName, ContentXElement appendageDefinition=null, Ragdoll ragdoll=null)
override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
AfflictionHusk(AfflictionPrefab prefab, float strength)
override float???? Strength
static Identifier GetNonHuskedSpeciesName(CharacterParams character, AfflictionPrefabHusk prefab)
static Identifier GetHuskedSpeciesName(CharacterParams character, AfflictionPrefabHusk prefab)
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 TransformThresholdOnDeath
The minimum strength the affliction must have for the affected character to transform into a husk upo...
readonly float DormantThreshold
The minimum strength at which husk infection will be in the dormant stage. It must be less than or eq...
readonly float TransitionThreshold
The minimum strength at which husk infection will be in its final stage. It must be greater than or e...
readonly Identifier HuskedSpeciesName
The species of husk to convert the affected character to once husk infection reaches its final stage.
readonly float ActiveThreshold
The minimum strength at which husk infection will be in the active stage. It must be greater than or ...
readonly Character Character
Limb GetAfflictionLimb(Affliction affliction)
IReadOnlyCollection< Affliction > GetAllAfflictions()
void RecalculateVitality()
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
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, bool ignoreDamageOverlay=false, bool recalculateVitality=true)
CharacterInventory Inventory
readonly AnimController AnimController
XElement Save(XElement parentElement)
Contains character data that should be editable in the character editor.
override ContentXElement? MainElement
Identifier NonHuskedSpecies
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
bool IsRunning
Is a round currently running?
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
Limb GetLimb(LimbType limbType, bool excludeSevered=true, bool excludeLimbsWithSecondaryType=false, bool useSecondaryType=false)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
void RemoveLimb(Limb limb)
@ Character
Characters only