7 using FarseerPhysics.Dynamics;
8 using Microsoft.Xna.Framework;
10 using System.Collections.Generic;
11 using System.Collections.Immutable;
12 using System.Diagnostics;
14 using System.Xml.Linq;
33 public readonly
static List<Character>
CharacterList =
new List<Character>();
36 private static int characterUpdateTick = 1;
41 partial
void UpdateLimbLightSource(
Limb limb);
43 private bool enabled =
true;
52 if (value == enabled) {
return; }
65 if (limb.
body !=
null)
69 UpdateLimbLightSource(limb);
73 if (item.body ==
null) {
continue; }
76 item.body.Enabled =
false;
78 else if (item.GetComponent<
Holdable>() is { IsActive: true })
82 item.body.Enabled =
true;
91 private bool disabledByEvent;
97 get {
return disabledByEvent; }
100 if (value == disabledByEvent) {
return; }
101 disabledByEvent = value;
164 public readonly Dictionary<Identifier, SerializableProperty>
Properties;
180 get {
return humanPrefab; }
183 if (humanPrefab == value) {
return; }
186 if (humanPrefab !=
null)
201 private Identifier? faction;
205 set { faction = value; }
211 get {
return teamID; }
215 if (info !=
null) { info.
TeamID = value; }
223 get {
return originalTeamID ?? teamID; }
232 ThrowIfAccessingWalletsInSingleplayer();
237 ThrowIfAccessingWalletsInSingleplayer();
242 public readonly HashSet<LatchOntoAI>
Latchers =
new HashSet<LatchOntoAI>();
245 protected readonly Dictionary<string, ActiveTeamChange>
activeTeamChanges =
new Dictionary<string, ActiveTeamChange>();
247 private const string OriginalChangeTeamIdentifier =
"original";
249 private void ThrowIfAccessingWalletsInSingleplayer()
256 throw new InvalidOperationException($
"Tried to access crew wallets in singleplayer. Use {nameof(CampaignMode)}.{nameof(CampaignMode.Bank)} or {nameof(CampaignMode)}.{nameof(CampaignMode.GetWallet)} instead.");
269 if (newTeam == teamID) {
return; }
270 if (originalTeamID ==
null) { originalTeamID = teamID; }
272 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient)
277 var order = OrderPrefab.Dismissal.CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver:
this).WithManualPriority(CharacterInfo.HighestManualOrderPriority);
278 SetOrder(order, isNewOrder:
true, speak:
false);
281 GameMain.NetworkMember.CreateEntityEvent(
this,
new TeamChangeEventData());
304 DebugConsole.ThrowError(
"Tried to add an existing team change! Make sure to check if the team change exists first.");
331 if (bestTeamChange.TeamChangePriority < desiredTeamChange.Value.TeamChangePriority)
333 bestTeamChange = desiredTeamChange.Value;
336 if (
TeamID != bestTeamChange.DesiredTeamId)
338 ChangeTeam(bestTeamChange.DesiredTeamId);
341 if (bestTeamChange.AggressiveBehavior)
344 SetOrder(order, isNewOrder:
true, speak:
false);
375 private Vector2 cursorPosition;
382 private Character selectedCharacter, selectedBy;
384 private const int maxLastAttackerCount = 4;
392 private readonly List<Attacker> lastAttackers =
new List<Attacker>();
404 get {
return itemSelectedDurations; }
406 private readonly Dictionary<ItemPrefab, double> itemSelectedDurations =
new Dictionary<ItemPrefab, double>();
407 private double itemSelectedTime;
429 public bool IsMale => info?.IsMale ??
false;
487 private float attackCoolDown;
492 private readonly Dictionary<ActionType, List<StatusEffect>> statusEffects =
new Dictionary<ActionType, List<StatusEffect>>();
512 viewTargetWorldPos =
new Vector2(
530 if (info !=
null && info != value)
548 return info !=
null && !
string.IsNullOrWhiteSpace(info.
Name) ? info.
Name :
SpeciesName.Value;
559 if (!
string.IsNullOrEmpty(petName)) {
return petName; }
562 if (info !=
null && !
string.IsNullOrWhiteSpace(info.
Name)) {
return info.
Name; }
564 if (displayName.IsNullOrWhiteSpace())
568 displayName = TextManager.Get($
"Character.{SpeciesName}");
572 displayName = TextManager.Get($
"Character.{Params.SpeciesTranslationOverride}");
575 return displayName.IsNullOrWhiteSpace() ?
Name : displayName.
Value;
589 private float hideFaceTimer;
594 return hideFaceTimer > 0.0f;
599 hideFaceTimer = MathHelper.Clamp(hideFaceTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
630 private Action<Character, Character> onCustomInteract;
639 private float lockHandsTimer;
644 return lockHandsTimer > 0.0f;
648 lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
654 HintManager.OnHandcuffed(
this);
677 get {
return cursorPosition; }
680 if (!MathUtils.IsValid(value)) {
return; }
681 cursorPosition = value;
700 get {
return selectedCharacter; }
703 if (value == selectedCharacter) {
return; }
704 if (selectedCharacter !=
null) { selectedCharacter.selectedBy =
null; }
705 selectedCharacter = value;
706 if (selectedCharacter !=
null) {selectedCharacter.selectedBy =
this; }
713 if (
IsPlayer && isServerOrSingleplayer && value is {
IsDead:
true,
Wallet: { Balance: var balance and > 0 } grabbedWallet })
717 if (mpCampaign !=
null &&
GameMain.Server is { ServerSettings: { } settings })
719 switch (settings.LootedMoneyDestination)
725 mpCampaign.Bank.Give(balance);
731 GameServer.Log($
"{GameServer.CharacterLogName(this)} grabbed {value.Name}'s body and received {grabbedWallet.Balance} mk.",
ServerLog.
MessageType.Money);
733 grabbedWallet.Deduct(balance);
736 if (mpCampaign !=
null && selectedCharacter.
Info !=
null)
738 var characterCampaignData = mpCampaign?.GetCharacterData(selectedCharacter.
Info);
739 if (characterCampaignData!=
null)
741 characterCampaignData.WalletData = grabbedWallet.Save();
742 characterCampaignData?.ApplyWalletData(selectedCharacter);
748 spCampaign.Bank.Give(balance);
750 grabbedWallet.Deduct(balance);
758 get {
return selectedBy; }
761 if (selectedBy !=
null)
762 selectedBy.selectedCharacter =
null;
764 if (selectedBy !=
null)
765 selectedBy.selectedCharacter =
this;
772 public IEnumerable<Item> HeldItems
778 if (item1 !=
null) { yield
return item1; }
779 if (item2 !=
null && item2 != item1) { yield
return item2; }
785 int rangedItemCount = 0;
786 foreach (var item
in HeldItems)
793 if (rangedItemCount > 1)
802 private float lowPassMultiplier;
803 public float LowPassMultiplier
805 get {
return lowPassMultiplier; }
806 set { lowPassMultiplier = MathHelper.Clamp(value, 0.0f, 1.0f); }
809 private float obstructVisionAmount;
810 public float ObstructVisionAmount
812 get {
return obstructVisionAmount; }
815 obstructVisionAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
822 public bool ObstructVision
826 return obstructVisionAmount > 0.01f;
830 obstructVisionAmount = value ? 0.5f : 0.0f;
834 private double pressureProtectionLastSet;
835 private float pressureProtection;
836 public float PressureProtection
838 get {
return pressureProtection; }
841 pressureProtection = Math.Max(value, pressureProtection);
842 pressureProtectionLastSet = Timing.TotalTime;
849 public bool InPressure
851 get {
return CurrentHull ==
null || CurrentHull.
LethalPressure > 0.0f; }
862 public const float KnockbackCooldown = 5.0f;
865 private float ragdollingLockTimer;
870 public bool IsIncapacitated
874 if (IsUnconscious) {
return true; }
879 public bool IsUnconscious
884 public bool IsHandcuffed
886 get {
return IsHuman && HasEquippedItem(Tags.HandLockerItem); }
899 if (!MathUtils.IsValid(value))
return;
904 public float OxygenAvailable
906 get {
return oxygenAvailable; }
907 set { oxygenAvailable = MathHelper.Clamp(value, 0.0f, 100.0f); }
910 public float HullOxygenPercentage
915 public bool UseHullOxygen {
get;
set; } =
true;
923 SetStun(value,
true);
936 public float MaxHealth => MaxVitality;
944 public float EmpVulnerability => Params.Health.EmpVulnerability;
945 public float PoisonVulnerability => Params.Health.PoisonVulnerability;
948 public float Bloodloss
953 if (!MathUtils.IsValid(value)) {
return; }
958 public float Bleeding
963 private bool speechImpedimentSet;
966 private float speechImpediment;
967 public float SpeechImpediment
971 if (!CanSpeak || IsUnconscious || IsKnockedDown) {
return 100.0f; }
972 return speechImpediment;
976 if (value < speechImpediment) {
return; }
977 speechImpedimentSet =
true;
978 speechImpediment = MathHelper.Clamp(value, 0.0f, 100.0f);
982 private float textChatVolume;
988 public float TextChatVolume
990 get => textChatVolume;
991 set => textChatVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
994 public float PressureTimer
1000 public float DisableImpactDamageTimer
1006 public bool IgnoreMeleeWeapons
1015 public float CurrentSpeed
1020 private Item _selectedItem;
1026 get => _selectedItem;
1029 var prevSelectedItem = _selectedItem;
1030 _selectedItem = value;
1031 if (value is not
null)
1036 HintManager.OnSetSelectedItem(
this, prevSelectedItem, _selectedItem);
1037 if (Controlled ==
this)
1039 _selectedItem?.GetComponent<
Fabricator>()?.RefreshSelectedItem();
1041 if (_selectedItem ==
null)
1050 _selectedItem?.GetComponent<CircuitBox>()?.OnViewUpdateProjSpecific();
1053 if (prevSelectedItem !=
null && (_selectedItem ==
null || _selectedItem != prevSelectedItem) && itemSelectedTime > 0)
1055 double selectedDuration = Timing.TotalTime - itemSelectedTime;
1056 if (itemSelectedDurations.ContainsKey(prevSelectedItem.Prefab))
1058 itemSelectedDurations[prevSelectedItem.Prefab] += selectedDuration;
1062 itemSelectedDurations.Add(prevSelectedItem.Prefab, selectedDuration);
1064 itemSelectedTime = 0;
1066 if (_selectedItem !=
null && (prevSelectedItem ==
null || prevSelectedItem != _selectedItem))
1068 itemSelectedTime = Timing.TotalTime;
1070 if (prevSelectedItem != _selectedItem && prevSelectedItem?.OnDeselect !=
null)
1072 prevSelectedItem.OnDeselect(
this);
1079 public Item SelectedSecondaryItem {
get;
set; }
1084 public bool HasSelectedAnyItem => SelectedItem !=
null || SelectedSecondaryItem !=
null;
1095 get {
return focusedItem; }
1096 set { focusedItem = value; }
1107 get {
return null; }
1110 private bool isDead;
1113 get {
return isDead; }
1116 if (isDead == value) {
return; }
1128 public bool EnableDespawn {
get;
set; } =
true;
1137 public bool CanBeSelected
1162 public bool GodMode =
false;
1164 public bool Unkillable
1173 public bool UseHealthWindow
1182 private bool accessRemovedCharacterErrorShown;
1183 public override Vector2 SimPosition
1189 if (!accessRemovedCharacterErrorShown)
1191 string errorMsg =
"Attempted to access a potentially removed character. Character: [name], id: " + ID +
", removed: " + Removed +
".";
1194 errorMsg +=
" AnimController == null";
1198 errorMsg +=
" AnimController.Collider == null";
1200 errorMsg +=
'\n' + Environment.StackTrace.CleanupStackTrace();
1201 DebugConsole.NewMessage(errorMsg.Replace(
"[name]", Name), Color.Red);
1202 GameAnalyticsManager.AddErrorEventOnce(
1203 "Character.SimPosition:AccessRemoved",
1204 GameAnalyticsManager.ErrorSeverity.Error,
1205 errorMsg.Replace(
"[name]", SpeciesName.Value) +
"\n" + Environment.StackTrace.CleanupStackTrace());
1206 accessRemovedCharacterErrorShown =
true;
1208 return Vector2.Zero;
1215 public override Vector2 Position
1217 get {
return ConvertUnits.ToDisplayUnits(SimPosition); }
1220 public override Vector2 DrawPosition
1229 public HashSet<Identifier> MarkedAsLooted =
new();
1234 public float AITurretPriority
1236 get => Params.AITurretPriority;
1237 private set => Params.AITurretPriority = value;
1257 return Create(characterInfo.
SpeciesName, position, seed, characterInfo,
id, isRemotePlayer, hasAi, createNetworkEvent:
true, ragdoll, spawnInitialItems);
1272 public static Character Create(
string speciesName, Vector2 position,
string seed,
CharacterInfo characterInfo =
null, ushort
id =
Entity.
NullEntityID,
bool isRemotePlayer =
false,
bool hasAi =
true,
bool createNetworkEvent =
true,
RagdollParams ragdoll =
null,
bool throwErrorIfNotFound =
true,
bool spawnInitialItems =
true)
1274 if (speciesName.EndsWith(
".xml", StringComparison.OrdinalIgnoreCase))
1276 speciesName = Path.GetFileNameWithoutExtension(speciesName);
1278 return Create(speciesName.ToIdentifier(), position, seed, characterInfo,
id, isRemotePlayer, hasAi, createNetworkEvent, ragdoll, throwErrorIfNotFound, spawnInitialItems);
1281 public static Character Create(Identifier speciesName, Vector2 position,
string seed,
CharacterInfo characterInfo =
null, ushort
id =
Entity.
NullEntityID,
bool isRemotePlayer =
false,
bool hasAi =
true,
bool createNetworkEvent =
true,
RagdollParams ragdoll =
null,
bool throwErrorIfNotFound =
true,
bool spawnInitialItems =
true)
1286 string errorMsg = $
"Failed to create character \"{speciesName}\". Matching prefab not found.\n" + Environment.StackTrace;
1287 if (throwErrorIfNotFound)
1289 DebugConsole.ThrowError(errorMsg);
1293 DebugConsole.AddWarning(errorMsg);
1298 return Create(prefab, position, seed, characterInfo,
id, isRemotePlayer, hasAi, createNetworkEvent, ragdoll, spawnInitialItems);
1306 var aiCharacter =
new AICharacter(prefab, position, seed, characterInfo,
id, isRemotePlayer, ragdoll, spawnInitialItems);
1311 aiCharacter.SetAI(ai);
1312 newCharacter = aiCharacter;
1316 newCharacter =
new Character(prefab, position, seed, characterInfo,
id, isRemotePlayer, ragdoll, spawnInitialItems);
1320 if (
GameMain.Server !=
null && Spawner !=
null && createNetworkEvent)
1328 return newCharacter;
1334 wallet =
new Wallet(Option<Character>.Some(
this));
1337 wallet.SetRewardDistribution(bank.RewardDistribution);
1341 this.Prefab = prefab;
1344 IsRemotePlayer = isRemotePlayer;
1346 oxygenAvailable = 100.0f;
1349 lowPassMultiplier = 1.0f;
1355 Info = characterInfo;
1361 if (!VariantOf.IsEmpty)
1363 DebugConsole.ThrowError(
"The variant system does not yet support humans, sorry. It does support other humanoids though!",
1366 if (characterInfo ==
null)
1373 teamID = Info.TeamID;
1375 Info.IsNewHire =
false;
1377 keys =
new Key[Enum.GetNames(typeof(
InputType)).Length];
1378 for (
int i = 0; i < Enum.GetNames(typeof(
InputType)).Length; i++)
1384 InitProjSpecific(mainElement);
1386 List<ContentXElement> inventoryElements =
new List<ContentXElement>();
1387 List<float> inventoryCommonness =
new List<float>();
1388 List<ContentXElement> healthElements =
new List<ContentXElement>();
1389 List<float> healthCommonness =
new List<float>();
1390 foreach (var subElement
in mainElement.Elements())
1392 switch (subElement.Name.ToString().ToLowerInvariant())
1395 inventoryElements.Add(subElement);
1396 inventoryCommonness.Add(subElement.GetAttributeFloat(
"commonness", 1.0f));
1399 healthElements.Add(subElement);
1400 healthCommonness.Add(subElement.GetAttributeFloat(
"commonness", 1.0f));
1402 case "statuseffect":
1404 if (statusEffect !=
null)
1406 if (!statusEffects.ContainsKey(statusEffect.type))
1408 statusEffects.Add(statusEffect.type,
new List<StatusEffect>());
1410 statusEffects[statusEffect.type].Add(statusEffect);
1415 if (Params.VariantFile !=
null && Params.MainElement is
ContentXElement paramsMainElement)
1417 var overrideElement = Params.VariantFile.GetRootExcludingOverride().FromPackage(paramsMainElement.ContentPackage);
1419 if (overrideElement.GetChildElement(
"inventory") !=
null)
1421 inventoryElements.Clear();
1422 inventoryCommonness.Clear();
1423 foreach (var subElement
in overrideElement.GetChildElements(
"inventory"))
1425 switch (subElement.Name.ToString().ToLowerInvariant())
1428 inventoryElements.Add(subElement);
1429 inventoryCommonness.Add(subElement.GetAttributeFloat(
"commonness", 1.0f));
1434 if (overrideElement.GetChildElement(
"health") !=
null)
1436 healthElements.Clear();
1437 healthCommonness.Clear();
1438 foreach (var subElement
in overrideElement.GetChildElements(
"health"))
1440 healthElements.Add(subElement);
1441 healthCommonness.Add(subElement.GetAttributeFloat(
"commonness", 1.0f));
1446 if (inventoryElements.Count > 0)
1449 inventoryElements.Count == 1 ? inventoryElements[0] : ToolBox.SelectWeightedRandom(inventoryElements, inventoryCommonness, random),
1453 if (healthElements.Count == 0)
1459 var selectedHealthElement = healthElements.Count == 1 ? healthElements[0] : ToolBox.SelectWeightedRandom(healthElements, healthCommonness, random);
1461 var limbHealthElement = selectedHealthElement;
1462 if (Params.VariantFile !=
null && limbHealthElement.GetChildElement(
"limb") ==
null)
1464 limbHealthElement = Params.OriginalElement.GetChildElement(
"health");
1469 if (Params.Husk && speciesName !=
"husk" &&
Prefab.VariantOf !=
"husk")
1471 Identifier nonHuskedSpeciesName = Identifier.Empty;
1476 if (huskPrefab.TargetSpecies.Contains(nonHuskedName))
1479 if (huskedSpeciesName.Equals(speciesName))
1481 nonHuskedSpeciesName = nonHuskedName;
1482 matchingAffliction = huskPrefab;
1487 if (matchingAffliction ==
null || nonHuskedSpeciesName.IsEmpty)
1489 DebugConsole.ThrowError($
"Cannot find a husk infection that matches {speciesName}! Please make sure that the speciesname is added as 'targets' in the husk affliction prefab definition!\n"
1490 +
"Note that all the infected speciesnames and files must stick the following pattern: [nonhuskedspeciesname][huskedspeciesname]. E.g. Humanhusk, Crawlerhusk, or Humancustomhusk, or Crawlerzombie. Not \"Customhumanhusk!\" or \"Zombiecrawler\"",
1494 speciesName = nonHuskedSpeciesName;
1496 if (ragdollParams ==
null && prefab.VariantOf ==
null)
1498 Identifier name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName;
1501 if (Params.HasInfo && info ==
null)
1506 else if (Params.HasInfo && info ==
null)
1521 PressureProtection =
int.MaxValue;
1529 CharacterList.Add(
this);
1540 LoadHeadAttachments();
1542 ApplyStatusEffects(
ActionType.OnSpawn, 1.0f);
1546 public void ReloadHead(
int? headId =
null,
int hairIndex = -1,
int beardIndex = -1,
int moustacheIndex = -1,
int faceAttachmentIndex = -1)
1548 if (Info ==
null) {
return; }
1550 if (head ==
null) {
return; }
1551 HashSet<Identifier> tags = Info.Head.Preset.TagSet.ToHashSet();
1552 if (headId.HasValue)
1554 tags.RemoveWhere(t => t.StartsWith(
"variant"));
1555 tags.Add($
"variant{headId.Value}".ToIdentifier());
1557 var oldHeadInfo = Info.Head;
1558 Info.RecreateHead(tags.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
1559 if (hairIndex == -1)
1561 Info.Head.HairIndex = oldHeadInfo.HairIndex;
1563 if (beardIndex == -1)
1565 Info.Head.BeardIndex = oldHeadInfo.BeardIndex;
1567 if (moustacheIndex == -1)
1569 Info.Head.MoustacheIndex = oldHeadInfo.MoustacheIndex;
1571 if (faceAttachmentIndex == -1)
1573 Info.Head.FaceAttachmentIndex = oldHeadInfo.FaceAttachmentIndex;
1575 Info.Head.SkinColor = oldHeadInfo.SkinColor;
1576 Info.Head.HairColor = oldHeadInfo.HairColor;
1577 Info.Head.FacialHairColor = oldHeadInfo.FacialHairColor;
1582 LoadHeadAttachments();
1587 if (Info ==
null) {
return; }
1590 if (head ==
null) {
return; }
1593 head.OtherWearables.Clear();
1597 Info.Head.FaceAttachment?.GetChildElements(
"sprite").ForEach(s => head.OtherWearables.Add(
new WearableSprite(s,
WearableType.FaceAttachment)));
1599 Info.Head.BeardElement?.GetChildElements(
"sprite").ForEach(s => head.OtherWearables.Add(
new WearableSprite(s,
WearableType.Beard)));
1601 Info.Head.MoustacheElement?.GetChildElements(
"sprite").ForEach(s => head.OtherWearables.Add(
new WearableSprite(s,
WearableType.Moustache)));
1603 Info.Head.HairElement?.GetChildElements(
"sprite").ForEach(s => head.OtherWearables.Add(
new WearableSprite(s,
WearableType.Hair)));
1610 head.EnableHuskSprite = Params.Husk;
1611 head.LoadHerpesSprite();
1612 head.UpdateWearableTypesToHide();
1619 if (
GameMain.Server !=
null && IsRemotePlayer)
1624 return dequeuedInput.HasFlag(InputNetFlags.Left) && !prevDequeuedInput.HasFlag(InputNetFlags.Left);
1626 return dequeuedInput.HasFlag(InputNetFlags.Right) && !prevDequeuedInput.HasFlag(InputNetFlags.Right);
1628 return dequeuedInput.HasFlag(InputNetFlags.Up) && !prevDequeuedInput.HasFlag(InputNetFlags.Up);
1630 return dequeuedInput.HasFlag(InputNetFlags.Down) && !prevDequeuedInput.HasFlag(InputNetFlags.Down);
1632 return dequeuedInput.HasFlag(InputNetFlags.Run) && prevDequeuedInput.HasFlag(InputNetFlags.Run);
1634 return dequeuedInput.HasFlag(InputNetFlags.Crouch) && !prevDequeuedInput.HasFlag(InputNetFlags.Crouch);
1636 return dequeuedInput.HasFlag(InputNetFlags.Select);
1638 return dequeuedInput.HasFlag(InputNetFlags.Deselect);
1640 return dequeuedInput.HasFlag(InputNetFlags.Health);
1642 return dequeuedInput.HasFlag(InputNetFlags.Grab);
1644 return dequeuedInput.HasFlag(InputNetFlags.Use) && !prevDequeuedInput.HasFlag(InputNetFlags.Use);
1646 return dequeuedInput.HasFlag(InputNetFlags.Shoot) && !prevDequeuedInput.HasFlag(InputNetFlags.Shoot);
1648 return dequeuedInput.HasFlag(InputNetFlags.Ragdoll) && !prevDequeuedInput.HasFlag(InputNetFlags.Ragdoll);
1655 return keys[(int)inputType].Hit;
1661 if (
GameMain.Server !=
null && IsRemotePlayer)
1666 return dequeuedInput.HasFlag(InputNetFlags.Left);
1668 return dequeuedInput.HasFlag(InputNetFlags.Right);
1670 return dequeuedInput.HasFlag(InputNetFlags.Up);
1672 return dequeuedInput.HasFlag(InputNetFlags.Down);
1674 return dequeuedInput.HasFlag(InputNetFlags.Run);
1676 return dequeuedInput.HasFlag(InputNetFlags.Crouch);
1682 return dequeuedInput.HasFlag(InputNetFlags.Aim);
1684 return dequeuedInput.HasFlag(InputNetFlags.Use);
1686 return dequeuedInput.HasFlag(InputNetFlags.Shoot);
1688 return dequeuedInput.HasFlag(InputNetFlags.Attack);
1690 return dequeuedInput.HasFlag(InputNetFlags.Ragdoll);
1699 if (invertControls !=
null)
1719 return keys[(int)inputType].Held;
1724 keys[(int)inputType].Hit = hit;
1725 keys[(int)inputType].Held = held;
1726 keys[(int)inputType].SetState(hit, held);
1731 keys[(int)inputType].Hit =
false;
1732 keys[(int)inputType].Held =
false;
1737 if (keys ==
null)
return;
1738 foreach (
Key key
in keys)
1748 return (info !=
null && !
string.IsNullOrWhiteSpace(info.
Name)) ? info.
Name : SpeciesName.Value;
1750 return SpeciesName.Value;
1756 if (info ==
null) {
return; }
1757 if (info.HumanPrefabIds !=
default)
1759 var humanPrefab = NPCSet.Get(info.HumanPrefabIds.NpcSetIdentifier, info.HumanPrefabIds.NpcIdentifier);
1760 if (humanPrefab ==
null)
1762 DebugConsole.ThrowError($
"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\".");
1764 else if (humanPrefab.GiveItems(
this, spawnPoint?.Submarine ??
Submarine, spawnPoint))
1775 if (info?.
Job ==
null || spawnPoint ==
null) {
return; }
1779 var idCard = item?.GetComponent<
IdCard>();
1780 if (idCard ==
null) {
continue; }
1783 if (idCard.OwnerName != info.
Name) {
continue; }
1784 foreach (
string s
in spawnPoint.IdCardTags)
1796 GetSkillLevel(skillIdentifier.ToIdentifier());
1798 private static readonly ImmutableDictionary<Identifier, StatTypes> overrideStatTypes =
new Dictionary<Identifier, StatTypes>
1800 {
new(
"helm"),
StatTypes.HelmSkillOverride },
1801 {
new(
"medical"),
StatTypes.MedicalSkillOverride },
1802 {
new(
"weapons"), StatTypes.WeaponsSkillOverride },
1803 {
new(
"electrical"),
StatTypes.ElectricalSkillOverride },
1804 {
new(
"mechanical"),
StatTypes.MechanicalSkillOverride }
1805 }.ToImmutableDictionary();
1809 if (Info?.
Job ==
null) {
return 0.0f; }
1810 float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
1812 if (overrideStatTypes.TryGetValue(skillIdentifier, out
StatTypes statType))
1814 float skillOverride = GetStatValue(statType);
1815 if (skillOverride > skillLevel)
1817 skillLevel = skillOverride;
1827 if (skillIdentifier !=
null)
1831 if (item?.GetComponent<Wearable>() is
Wearable wearable &&
1834 foreach (var allowedSlot
in wearable.AllowedSlots)
1837 if (!
Inventory.IsInLimbSlot(item, allowedSlot)) {
continue; }
1838 if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out
float skillValue))
1840 skillLevel += skillValue;
1849 skillLevel += GetStatValue(GetSkillStatType(skillIdentifier));
1850 return Math.Max(skillLevel, 0);
1854 public Vector2? OverrideMovement {
get;
set; }
1855 public bool ForceRun {
get;
set; }
1861 Vector2 targetMovement = Vector2.Zero;
1862 if (OverrideMovement.HasValue)
1864 targetMovement = OverrideMovement.Value;
1868 if (IsKeyDown(
InputType.Left)) { targetMovement.X -= 1.0f; }
1869 if (IsKeyDown(
InputType.Right)) { targetMovement.X += 1.0f; }
1870 if (IsKeyDown(
InputType.Up)) { targetMovement.Y += 1.0f; }
1871 if (IsKeyDown(
InputType.Down)) { targetMovement.Y -= 1.0f; }
1886 public bool CanRun => CanRunWhileDragging() &&
1893 if (selectedCharacter is not { IsDraggable:
true }) {
return true; }
1895 if (!selectedCharacter.
IsIncapacitated && selectedCharacter.
Stun <= 0.0f) {
return false; }
1896 return HasAbilityFlag(
AbilityFlags.MoveNormallyWhileDragging);
1905 float length = targetMovement.Length();
1908 targetMovement /= length;
1911 targetMovement *= currentSpeed;
1912 float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed);
1913 targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
1914 targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
1915 SpeedMultiplier = Math.Max(0.0f, greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier));
1916 targetMovement *= SpeedMultiplier;
1918 ResetSpeedMultiplier();
1919 return targetMovement;
1922 private float greatestNegativeSpeedMultiplier = 1f;
1923 private float greatestPositiveSpeedMultiplier = 1f;
1928 public float SpeedMultiplier {
get;
private set; } = 1;
1931 private double propulsionSpeedMultiplierLastSet;
1932 private float propulsionSpeedMultiplier;
1936 public float PropulsionSpeedMultiplier
1938 get {
return propulsionSpeedMultiplier; }
1941 propulsionSpeedMultiplier = value;
1942 propulsionSpeedMultiplierLastSet = Timing.TotalTime;
1948 greatestNegativeSpeedMultiplier = Math.Min(val, greatestNegativeSpeedMultiplier);
1949 greatestPositiveSpeedMultiplier = Math.Max(val, greatestPositiveSpeedMultiplier);
1954 greatestPositiveSpeedMultiplier = 1f;
1955 greatestNegativeSpeedMultiplier = 1f;
1956 if (Timing.TotalTime > propulsionSpeedMultiplierLastSet + 0.1)
1958 propulsionSpeedMultiplier = 1.0f;
1962 private float greatestNegativeHealthMultiplier = 1f;
1963 private float greatestPositiveHealthMultiplier = 1f;
1968 public float HealthMultiplier {
get;
private set; } = 1;
1972 greatestNegativeHealthMultiplier = Math.Min(val, greatestNegativeHealthMultiplier);
1973 greatestPositiveHealthMultiplier = Math.Max(val, greatestPositiveHealthMultiplier);
1976 private void CalculateHealthMultiplier()
1978 HealthMultiplier = greatestPositiveHealthMultiplier - (1f - greatestNegativeHealthMultiplier);
1980 greatestPositiveHealthMultiplier = 1f;
1981 greatestNegativeHealthMultiplier = 1f;
1987 public float HumanPrefabHealthMultiplier {
get;
private set; } = 1;
1994 float reduction = 0;
2008 int totalTailLimbs = 0;
2009 int destroyedTailLimbs = 0;
2017 destroyedTailLimbs++;
2021 if (destroyedTailLimbs > 0)
2023 reduction += MathHelper.Lerp(0,
AnimController.
InWater ? 1f : 0.5f, (
float)destroyedTailLimbs / totalTailLimbs);
2026 return Math.Clamp(reduction, 0, 1f);
2029 private float CalculateMovementPenalty(
Limb limb,
float sum,
float max = 0.8f)
2035 return Math.Clamp(sum, 0, 1f);
2043 float sum = startSum;
2050 sum += CalculateMovementPenalty(limb, sum, max: 0.5f);
2054 return Math.Clamp(sum, 0, 1f);
2068 speed *= 1f - MathHelper.Lerp(0, max, GetTemporarySpeedReduction());
2075 private const float cursorFollowMargin = 40;
2080 if (!AllowInput) {
return; }
2084 SmoothedCursorPosition = cursorPosition;
2090 Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
2091 smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff);
2092 SmoothedCursorPosition = cursorPosition - smoothedCursorDiff;
2095 bool aiControlled =
this is
AICharacter && Controlled !=
this && !IsRemotelyControlled;
2098 Vector2 targetMovement = GetTargetMovement();
2105 humanAnimController.Crouching =
2106 humanAnimController.ForceSelectAnimationType ==
AnimationType.Crouch ||
2110 humanAnimController.ForceSelectAnimationType =
AnimationType.NotDefined;
2114 if (!aiControlled &&
2121 if (dontFollowCursor)
2144 if (dequeuedInput.HasFlag(InputNetFlags.FacingLeft))
2156 if (memState.Count > 0)
2169 enemyAI.LatchOntoAI?.DeattachFromBody(reset:
true);
2178 enemyAi.AimRangedAttack(attack, currentAttackTarget.DamageTarget as
Entity);
2182 if (attackCoolDown > 0.0f)
2184 attackCoolDown -= deltaTime;
2190 if ((currentAttackTarget.DamageTarget as
Entity)?.Removed ??
false)
2192 currentAttackTarget =
default;
2194 currentAttackTarget.AttackLimb?.UpdateAttack(deltaTime, currentAttackTarget.AttackPos, currentAttackTarget.DamageTarget, out _);
2199 Vector2 attackPos = SimPosition + ConvertUnits.ToSimUnits(cursorPosition - Position);
2207 Physics.CollisionCharacter | Physics.CollisionWall);
2217 SimPosition - ((
Submarine)body.UserData).SimPosition,
2218 attackPos - ((
Submarine)body.UserData).SimPosition,
2220 Physics.CollisionWall);
2232 attackTarget = damageable;
2234 else if (body.UserData is
Limb limb)
2236 attackTarget = limb.character;
2240 var currentContexts = GetAttackContexts();
2243 if (l.IsSevered || l.IsStuck) { return false; }
2244 if (l.Disabled) { return false; }
2245 var attack = l.attack;
2246 if (attack ==
null) {
return false; }
2247 if (attack.CoolDownTimer > 0) { return false; }
2248 if (!attack.IsValidContext(currentContexts)) { return false; }
2249 if (attackTarget !=
null)
2251 if (!attack.IsValidTarget(attackTarget as
Entity)) { return false; }
2254 if (attack.Conditionals.Any(c => !c.TargetSelf && !c.Matches(se))) { return false; }
2257 if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(
this))) { return false; }
2260 var sortedLimbs = validLimbs.OrderBy(l => Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(l.SimPosition), cursorPosition));
2262 var attackLimb = sortedLimbs.FirstOrDefault();
2263 if (attackLimb !=
null)
2265 if (attackTarget is
Character targetCharacter)
2268 foreach (
Limb limb
in targetCharacter.AnimController.Limbs)
2271 float tempDist = ConvertUnits.ToDisplayUnits(Vector2.Distance(limb.
SimPosition, attackLimb.SimPosition));
2272 if (tempDist < dist)
2279 if (!attackLimb.attack.IsRunning)
2281 attackCoolDown = 1.0f;
2291 foreach (
Item item
in HeldItems)
2293 if (!CanInteractWith(item)) {
continue; }
2295 if (SelectedItem?.OwnInventory !=
null && SelectedItem.OwnInventory.CanBePut(item))
2297 SelectedItem.OwnInventory.TryPutItem(item,
this);
2309 if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
2311 foreach (
Item item
in HeldItems)
2313 tryUseItem(item, deltaTime);
2317 if (item.GetComponent<
Wearable>() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
2319 tryUseItem(item, deltaTime);
2325 void tryUseItem(
Item item,
float deltaTime)
2335 item.
Use(deltaTime, user:
this);
2342 item.
Use(deltaTime, user:
this);
2347 HintManager.OnShootWithoutAiming(
this, item);
2353 if (SelectedItem !=
null)
2355 tryUseItem(SelectedItem, deltaTime);
2358 if (SelectedCharacter !=
null)
2360 if (!SelectedCharacter.CanBeSelected ||
2361 (Vector2.DistanceSquared(SelectedCharacter.WorldPosition, WorldPosition) > MaxDragDistance * MaxDragDistance &&
2362 SelectedCharacter.GetDistanceToClosestLimb(GetRelativeSimPosition(selectedCharacter, WorldPosition)) > ConvertUnits.ToSimUnits(MaxDragDistance)))
2364 DeselectCharacter();
2368 if (IsRemotelyControlled && keys !=
null)
2370 foreach (
Key key
in keys)
2377 private struct AttackTargetData
2379 public Limb AttackLimb {
get;
set; }
2380 public IDamageable DamageTarget {
get;
set; }
2381 public Vector2 AttackPos {
get;
set; }
2384 private AttackTargetData currentAttackTarget;
2387 currentAttackTarget =
new AttackTargetData()
2389 AttackLimb = attackLimb,
2390 DamageTarget = damageTarget,
2391 AttackPos = attackPos
2395 private Limb GetSeeingLimb()
2403 if (target is
Character targetCharacter)
2405 return IsCharacterVisible(targetCharacter, seeingEntity, seeThroughWindows, checkFacing);
2409 return CheckVisibility(target, seeingEntity, seeThroughWindows, checkFacing);
2415 if (seeingEntity is
Character seeingCharacter)
2417 return seeingCharacter.CanSeeTarget(target, seeThroughWindows: seeThroughWindows, checkFacing: checkFacing);
2419 if (target is
Character targetCharacter)
2421 return IsCharacterVisible(targetCharacter, seeingEntity, seeThroughWindows, checkFacing);
2425 return CheckVisibility(target, seeingEntity, seeThroughWindows, checkFacing);
2429 private static bool IsCharacterVisible(
Character target,
ISpatialEntity seeingEntity,
bool seeThroughWindows =
false,
bool checkFacing =
false)
2431 System.Diagnostics.Debug.Assert(target !=
null);
2432 if (target ==
null || target.
Removed) {
return false; }
2433 if (CheckVisibility(target, seeingEntity, seeThroughWindows, checkFacing)) {
return true; }
2437 Limb leftExtremity =
null, rightExtremity =
null;
2438 float leftMostDot = 0.0f, rightMostDot = 0.0f;
2440 Vector2 leftDir =
new Vector2(dir.Y, -dir.X);
2441 Vector2 rightDir =
new Vector2(-dir.Y, dir.X);
2445 if (limb.Hidden) {
continue; }
2446 Vector2 limbDir = limb.WorldPosition - seeingEntity.
WorldPosition;
2447 float leftDot = Vector2.Dot(limbDir, leftDir);
2448 if (leftDot > leftMostDot)
2450 leftMostDot = leftDot;
2451 leftExtremity = limb;
2454 float rightDot = Vector2.Dot(limbDir, rightDir);
2455 if (rightDot > rightMostDot)
2457 rightMostDot = rightDot;
2458 rightExtremity = limb;
2462 if (leftExtremity !=
null && CheckVisibility(leftExtremity, seeingEntity, seeThroughWindows, checkFacing)) {
return true; }
2463 if (rightExtremity !=
null && CheckVisibility(rightExtremity, seeingEntity, seeThroughWindows, checkFacing)) {
return true; }
2468 private static bool CheckVisibility(ISpatialEntity target, ISpatialEntity seeingEntity,
bool seeThroughWindows =
true,
bool checkFacing =
false)
2470 System.Diagnostics.Debug.Assert(target !=
null);
2471 if (target ==
null) {
return false; }
2472 if (seeingEntity ==
null) {
return false; }
2474 Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - seeingEntity.WorldPosition);
2475 if (checkFacing && seeingEntity is Character seeingCharacter)
2477 if (Math.Sign(diff.X) != seeingCharacter.AnimController.Dir) {
return false; }
2481 if (target.Submarine == seeingEntity.Submarine || target.Submarine ==
null)
2483 return Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff, blocksVisibilityPredicate: IsBlocking) ==
null;
2486 else if (seeingEntity.Submarine ==
null)
2488 return Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff, blocksVisibilityPredicate: IsBlocking) ==
null;
2494 Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff, blocksVisibilityPredicate: IsBlocking) ==
null &&
2495 Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff, blocksVisibilityPredicate: IsBlocking) ==
null;
2498 bool IsBlocking(Fixture f)
2501 if (body ==
null) {
return false; }
2502 if (body.UserData is Structure wall)
2504 if (!wall.CastShadow && seeThroughWindows) {
return false; }
2505 return wall != target;
2507 else if (body.UserData is Item item)
2509 if (item.GetComponent<
Door>() is { HasWindow: true } door && seeThroughWindows)
2511 if (door.IsPositionOnWindow(ConvertUnits.ToDisplayUnits(
Submarine.LastPickedPosition))) {
return false; }
2514 return item != target;
2525 public bool HasItem(
Item item,
bool requireEquipped =
false,
InvSlotType? slotType =
null) => requireEquipped ? HasEquippedItem(item, slotType) : item.IsOwnedBy(this);
2529 if (
Inventory ==
null) {
return false; }
2533 if (predicate !=
null)
2535 if (!predicate(slot)) {
continue; }
2537 if (slotType.HasValue)
2539 if (!slotType.Value.HasFlag(slot)) {
continue; }
2552 if (
Inventory ==
null) {
return false; }
2555 if (slotType.HasValue)
2557 if (!slotType.Value.HasFlag(
Inventory.SlotTypes[i])) {
continue; }
2564 if (item ==
null) {
continue; }
2565 if (!allowBroken && item.Condition <= 0.0f) {
continue; }
2566 if (item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) {
return true; }
2576 if (slotType.HasValue)
2578 if (!slotType.Value.HasFlag(
Inventory.SlotTypes[i])) {
continue; }
2585 if (item ==
null) {
continue; }
2586 if (tagOrIdentifier.IsEmpty || item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier))
2596 if (!CanInteract || inventory.
Locked) {
return false; }
2600 return inventoryOwner.IsInventoryAccessibleTo(
this, accessLevel) && (inventoryOwner ==
this || CanInteractWith(inventoryOwner));
2605 if (!CanInteractWith(item))
2608 foreach (var linkedEntity
in item.linkedTo)
2615 if (container !=
null)
2617 if (!container.
HasRequiredItems(
this, addMessage:
false)) {
return false; }
2625 !character.IsClimbing && !DisableHealthWindow &&
2626 UseHealthWindow && character.CanInteract &&
2627 (!checkFriendlyTeam || IsFriendly(character) || CanBeDraggedBy(character)) &&
2628 character.CanInteractWith(
this, 160f,
false);
2632 if (!IsDraggable) {
return false; }
2633 return IsKnockedDown || LockHands || IsPet || (IsBot && character.
TeamID == TeamID);
2641 if (Removed ||
Inventory ==
null) {
return false; }
2642 if (!
Inventory.AccessibleWhenAlive && !IsDead)
2644 if (character ==
this)
2650 if (character ==
this) {
return true; }
2651 if (IsKnockedDown || LockHands) {
return true; }
2652 return accessLevel
switch
2657 _ =>
throw new NotImplementedException()
2660 bool IsOnSameTeam() => character.
TeamID == teamID;
2661 bool IsFriendlyPet() => IsPet && character.
IsFriendly(
this);
2664 private Stopwatch sw;
2665 private Stopwatch StopWatch => sw ??=
new Stopwatch();
2666 private float _selectedItemPriority;
2667 private Item _foundItem;
2675 public bool FindItem(ref
int itemIndex, out
Item targetItem, IEnumerable<Identifier> identifiers =
null,
bool ignoreBroken =
true,
2676 IEnumerable<Item> ignoredItems =
null, IEnumerable<Identifier> ignoredContainerIdentifiers =
null,
2677 Func<Item, bool> customPredicate =
null, Func<Item, float> customPriorityFunction =
null,
float maxItemDistance = 10000,
ISpatialEntity positionalReference =
null)
2681 StopWatch.Restart();
2686 _selectedItemPriority = 0;
2688 int itemsPerFrame = IsOnPlayerTeam ? 100 : 10;
2689 int checkedItemCount = 0;
2690 for (
int i = 0; i < itemsPerFrame && itemIndex <
Item.
ItemList.Count; i++, itemIndex++)
2694 if (!item.IsInteractable(
this)) {
continue; }
2695 if (ignoredItems !=
null && ignoredItems.Contains(item)) {
continue; }
2696 if (item.Submarine ==
null) {
continue; }
2697 if (item.Submarine.TeamID != TeamID) {
continue; }
2698 if (item.CurrentHull ==
null) {
continue; }
2699 if (ignoreBroken && item.Condition <= 0) {
continue; }
2704 if (customPredicate !=
null && !customPredicate(item)) {
continue; }
2705 if (identifiers !=
null && identifiers.None(
id => item.Prefab.Identifier ==
id || item.HasTag(
id))) {
continue; }
2706 if (ignoredContainerIdentifiers !=
null && item.Container !=
null)
2708 if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) {
continue; }
2710 if (IsItemTakenBySomeoneElse(item)) {
continue; }
2711 Entity rootInventoryOwner = item.GetRootInventoryOwner();
2712 if (rootInventoryOwner is
Item ownerItem)
2714 if (!ownerItem.IsInteractable(
this)) {
continue; }
2716 float itemPriority = customPriorityFunction !=
null ? customPriorityFunction(item) : 1;
2717 if (itemPriority <= 0) {
continue; }
2718 Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
2719 Vector2 refPos = positionalReference !=
null ? positionalReference.WorldPosition : WorldPosition;
2720 float distanceFactor =
AIObjective.
GetDistanceFactor(refPos, itemPos, verticalDistanceMultiplier: 5, maxDistance: maxItemDistance, factorAtMaxDistance: 0);
2721 itemPriority *= distanceFactor;
2722 if (itemPriority > _selectedItemPriority)
2724 _selectedItemPriority = itemPriority;
2728 targetItem = _foundItem;
2732 var msg = $
"Went through {checkedItemCount} of total {Item.ItemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
2733 if (StopWatch.ElapsedMilliseconds > 5)
2735 DebugConsole.ThrowError(msg);
2740 DebugConsole.AddWarning(msg);
2753 if (!skipDistanceCheck)
2756 if (Vector2.DistanceSquared(SimPosition, c.
SimPosition) > maxDist * maxDist &&
2763 return !checkVisibility || CanSeeTarget(c);
2768 return CanInteractWith(item, out _, checkLinked);
2773 distanceToItem = -1.0f;
2779 if (!CanInteract || hidden || !item.
IsInteractable(
this)) {
return false; }
2786 Wire wire = item.GetComponent<
Wire>();
2790 if (wire.
Locked) {
return false; }
2803 if (SelectedItem?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ??
false)
2813 if (linked is
Item linkedItem &&
2818 if (CanInteractWith(linkedItem, out
float distToLinked, checkLinked:
false))
2820 distanceToItem = distToLinked;
2830 if (pickableComponent !=
null && pickableComponent.
Picker !=
this && pickableComponent.
Picker !=
null && !pickableComponent.
Picker.
IsDead) {
return false; }
2832 if (SelectedItem?.GetComponent<RemoteController>()?.TargetItem == item) {
return true; }
2835 if (heldItem1?.GetComponent<RemoteController>()?.TargetItem == item) {
return true; }
2837 if (heldItem2?.GetComponent<RemoteController>()?.TargetItem == item) {
return true; }
2841 Vector2 upperBodyPosition = Position + (characterDirection * 20.0f);
2842 Vector2 lowerBodyPosition = Position - (characterDirection * 60.0f);
2856 Vector2 playerDistanceCheckPosition =
2857 lowerBodyPosition.Y < upperBodyPosition.Y ?
2858 Vector2.Clamp(itemDisplayRect.Center.ToVector2(), lowerBodyPosition, upperBodyPosition) :
2859 Vector2.Clamp(itemDisplayRect.Center.ToVector2(), upperBodyPosition, lowerBodyPosition);
2862 if (itemDisplayRect.Contains(playerDistanceCheckPosition))
2864 distanceToItem = 0.0f;
2869 Vector2 rectIntersectionPoint =
new Vector2(
2870 MathHelper.Clamp(playerDistanceCheckPosition.X, itemDisplayRect.X, itemDisplayRect.Right),
2871 MathHelper.Clamp(playerDistanceCheckPosition.Y, itemDisplayRect.Y, itemDisplayRect.Bottom));
2872 distanceToItem = Vector2.Distance(rectIntersectionPoint, playerDistanceCheckPosition);
2879 float armLength = 0.75f * ConvertUnits.ToDisplayUnits(c.ArmLength);
2880 interactDistance = Math.Min(interactDistance, armLength);
2882 if (distanceToItem > interactDistance && item.
InteractDistance > 0.0f) {
return false; }
2894 if (SelectedSecondaryItem.GetComponent<
Controller>() is { ControlCharacterPose: true } selectedController)
2896 float threshold = ConvertUnits.ToSimUnits(cursorFollowMargin);
2905 bool itemCenterVisible = CheckBody(body, item);
2913 RectangleF simRect =
new RectangleF(
2914 x: ConvertUnits.ToSimUnits(transformTrigger.X),
2915 y: ConvertUnits.ToSimUnits(transformTrigger.Y - transformTrigger.Height),
2916 width: ConvertUnits.ToSimUnits(transformTrigger.Width),
2917 height: ConvertUnits.ToSimUnits(transformTrigger.Height));
2919 simRect.Location = GetPosition(
Submarine, item, simRect.Location);
2921 Vector2 closest = ToolBox.GetClosestPointOnRectangle(simRect, SimPosition);
2924 if (CheckBody(triggerBody, item)) {
return true; }
2929 return itemCenterVisible;
2936 static bool CheckBody(Body body,
Item item)
2938 if (body is
null) {
return true; }
2940 if (otherItem != item &&
2943 otherItem?.GetComponent<
Door>() is not { IsOpen:
true } &&
2952 static Vector2 GetPosition(
Submarine submarine,
Item item, Vector2 simPosition)
2954 Vector2 position = simPosition;
2957 Vector2 subPos = submarine?.
SimPosition ?? Vector2.Zero;
2959 if (submarine ==
null && item.
Submarine !=
null)
2962 position += itemSubPos;
2964 else if (submarine !=
null && item.
Submarine ==
null)
2969 else if (submarine != item.
Submarine && submarine !=
null)
2972 position += itemSubPos;
2987 this.onCustomInteract = onCustomInteract;
2988 CustomInteractHUDText = hudText;
2993 if (character ==
null || character ==
this) {
return; }
2994 SelectedCharacter = character;
2999 if (SelectedCharacter ==
null) {
return; }
3000 if (!SelectedCharacter.AllowInput)
3004 SelectedCharacter.AnimController?.ResetPullJoints();
3006 SelectedCharacter =
null;
3011 bool isLocalPlayer = Controlled ==
this;
3013 if (!isLocalPlayer && (
this is
AICharacter && !IsRemotePlayer))
3018 if (DisableInteract)
3020 DisableInteract =
false;
3026 SelectedItem = SelectedSecondaryItem =
null;
3030 FocusedCharacter =
null;
3031 if (SelectedCharacter !=
null) { DeselectCharacter(); }
3039 if (!IsMouseOnUI && (ViewTarget ==
null || ViewTarget ==
this) && !DisableFocusingOnEntities)
3045 FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) :
null;
3046 if (FocusedCharacter !=
null && !CanSeeTarget(FocusedCharacter)) { FocusedCharacter =
null; }
3047 float aimAssist = GameSettings.CurrentConfig.AimAssistAmount * (
AnimController.
InWater ? 1.5f : 1.0f);
3048 if (HeldItems.Any(it => it?.GetComponent<
Wire>()?.
IsActive ??
false))
3054 UpdateInteractablesInRange();
3058 focusedItem = CanInteract ? FindClosestItem(interactablesInRange, mouseSimPos, aimAssist) :
null;
3061 if (focusedItem !=
null)
3065 FocusedCharacter is { IsPet: true } && Vector2.DistanceSquared(focusedItem.
SimPosition, mouseSimPos) < Vector2.DistanceSquared(FocusedCharacter.SimPosition, mouseSimPos))
3067 FocusedCharacter =
null;
3070 findFocusedTimer = 0.05f;
3074 if (focusedItem !=
null && !CanInteractWith(focusedItem)) { focusedItem =
null; }
3075 if (FocusedCharacter !=
null && !CanInteractWith(FocusedCharacter)) { FocusedCharacter =
null; }
3081 FocusedCharacter =
null;
3084 findFocusedTimer -= deltaTime;
3085 DisableFocusingOnEntities =
false;
3089 bool headInWater = head ==
null ?
3093 Ladder currentLadder = SelectedSecondaryItem?.GetComponent<
Ladder>();
3094 if ((SelectedSecondaryItem ==
null || currentLadder !=
null) &&
3098 bool isControlled = Controlled ==
this;
3100 Ladder nearbyLadder =
null;
3101 if (isControlled || climbInput)
3103 float minDist =
float.PositiveInfinity;
3106 if (ladder == currentLadder)
3110 else if (currentLadder !=
null)
3119 if (CanInteractWith(ladder.
Item, out
float dist, checkLinked:
false) && dist < minDist)
3122 nearbyLadder = ladder;
3132 if (nearbyLadder !=
null && climbInput)
3134 if (nearbyLadder.
Select(
this))
3136 SelectedSecondaryItem = nearbyLadder.
Item;
3141 bool selectInputSameAsDeselect =
false;
3143 selectInputSameAsDeselect = GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Select] == GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Deselect];
3146 if (SelectedCharacter !=
null && (IsKeyHit(
InputType.Grab) || IsKeyHit(
InputType.Health)))
3148 DeselectCharacter();
3150 else if (FocusedCharacter !=
null && IsKeyHit(
InputType.Grab) && FocusedCharacter.CanBeDraggedBy(
this) && (CanInteract || FocusedCharacter.IsDead && CanEat))
3152 SelectCharacter(FocusedCharacter);
3154 else if (FocusedCharacter is { IsIncapacitated:
false } && IsKeyHit(
InputType.Use) && FocusedCharacter.IsPet && CanInteract)
3158 else if (FocusedCharacter !=
null && IsKeyHit(
InputType.Health) && FocusedCharacter.CanBeHealedBy(
this))
3160 if (FocusedCharacter == SelectedCharacter)
3162 DeselectCharacter();
3164 if (Controlled ==
this)
3172 SelectCharacter(FocusedCharacter);
3174 if (Controlled ==
this)
3176 HealingCooldown.PutOnCooldown();
3180 if (
GameMain.Server?.ConnectedClients is { } clients)
3182 foreach (
Client c
in clients)
3186 HealingCooldown.SetCooldown(c);
3193 else if (FocusedCharacter !=
null && IsKeyHit(
InputType.Use) && FocusedCharacter.onCustomInteract !=
null && FocusedCharacter.AllowCustomInteract)
3195 FocusedCharacter.onCustomInteract(FocusedCharacter,
this);
3197 else if (IsKeyHit(
InputType.Deselect) && SelectedItem !=
null &&
3198 (focusedItem ==
null || focusedItem == SelectedItem || !selectInputSameAsDeselect))
3200 SelectedItem =
null;
3205 else if (IsKeyHit(
InputType.Deselect) && SelectedSecondaryItem !=
null && SelectedSecondaryItem.GetComponent<
Ladder>() ==
null &&
3206 (focusedItem ==
null || focusedItem == SelectedSecondaryItem || !selectInputSameAsDeselect))
3208 ReleaseSecondaryItem();
3213 else if (IsKeyHit(
InputType.Health) && SelectedItem !=
null)
3215 SelectedItem =
null;
3217 else if (focusedItem !=
null)
3221 if (selectInputSameAsDeselect)
3228 if (Controlled ==
this)
3271 Spawner?.AddEntityToRemoveQueue(c);
3283 if (Controlled !=
null)
3285 distSqr = Math.Min(distSqr, Vector2.DistanceSquared(Controlled.WorldPosition, c.
WorldPosition));
3308 characterUpdateTick++;
3310 if (characterUpdateTick % CharacterUpdateInterval == 0)
3312 for (
int i = 0; i < CharacterList.Count; i++)
3316 CharacterList[i].Update(deltaTime * CharacterUpdateInterval, cam);
3322 if (character.
Removed) {
continue; }
3324 character.
Update(deltaTime, cam);
3328 UpdateSpeechBubbles(deltaTime);
3332 static partial
void UpdateSpeechBubbles(
float deltaTime);
3336 UpdateProjSpecific(deltaTime, cam);
3338 if (TextChatVolume > 0)
3340 TextChatVolume -= 0.2f * deltaTime;
3343 if (InvisibleTimer > 0.0f)
3345 if (Controlled ==
null || Controlled ==
this || (Controlled.CharacterHealth.GetAffliction(
"psychosis")?.Strength ?? 0.0f) <= 0.0f)
3347 InvisibleTimer = Math.Min(InvisibleTimer, 1.0f);
3349 InvisibleTimer -= deltaTime;
3352 KnockbackCooldownTimer -= deltaTime;
3356 UpdateDespawn(deltaTime);
3358 if (!Enabled) {
return; }
3371 ApplyStatusEffects(
ActionType.Always, deltaTime);
3373 PreviousHull = CurrentHull;
3374 CurrentHull =
Hull.
FindHull(WorldPosition, CurrentHull, useWorldCoordinates:
true);
3376 obstructVisionAmount = Math.Max(obstructVisionAmount - deltaTime, 0.0f);
3389 IgnoreMeleeWeapons =
false;
3391 UpdateSightRange(deltaTime);
3392 UpdateSoundRange(deltaTime);
3394 UpdateAttackers(deltaTime);
3396 foreach (var characterTalent
in characterTalents)
3398 characterTalent.UpdateTalent(deltaTime);
3401 if (IsDead) {
return; }
3412 DisableImpactDamageTimer -= deltaTime;
3414 if (!speechImpedimentSet)
3417 speechImpediment = 0.0f;
3419 speechImpedimentSet =
false;
3436 PressureTimer = 100.0f;
3444 if (PressureTimer >= 100.0f)
3446 if (Controlled ==
this) { cam.Zoom = 5.0f; }
3450 if (IsDead) {
return; }
3456 PressureTimer = 0.0f;
3468 if (IsDead) {
return; }
3474 ApplyStatusEffects(
ActionType.OnActive, deltaTime);
3477 if (aiTarget !=
null && Timing.TotalTime > aiTarget.InDetectableSetTime + 0.1f)
3479 aiTarget.InDetectable =
false;
3482 UpdateControlled(deltaTime, cam);
3487 UpdateOxygen(deltaTime);
3490 CalculateHealthMultiplier();
3493 if (IsIncapacitated)
3495 Stun = Math.Max(5.0f, Stun);
3497 SelectedItem = SelectedSecondaryItem =
null;
3501 UpdateAIChatMessages(deltaTime);
3503 bool wasRagdolled = IsRagdolled;
3504 if (IsForceRagdolled)
3506 IsRagdolled = IsForceRagdolled;
3508 else if (
this != Controlled)
3510 wasRagdolled = IsRagdolled;
3511 IsRagdolled = IsKeyDown(
InputType.Ragdoll);
3527 if (ragdollingLockTimer > 0.0f)
3529 ragdollingLockTimer -= deltaTime;
3531 else if (!tooFastToUnragdoll)
3533 IsRagdolled = IsKeyDown(
InputType.Ragdoll);
3536 ragdollingLockTimer = 0.2f;
3539 SetInput(
InputType.Ragdoll,
false, IsRagdolled);
3546 lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
3548 if (IsRagdolled || !CanMove)
3552 humanAnimController.Crouching =
false;
3556 SelectedItem = SelectedSecondaryItem =
null;
3562 Control(deltaTime, cam);
3564 bool isNotControlled = Controlled !=
this;
3566 if (isNotControlled && (!(
this is
AICharacter) || IsRemotePlayer))
3568 Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
3569 DoInteractionUpdate(deltaTime, mouseSimPos);
3572 if (MustDeselect(SelectedItem))
3574 SelectedItem =
null;
3576 if (MustDeselect(SelectedSecondaryItem))
3578 ReleaseSecondaryItem();
3581 if (!IsDead) { LockHands =
false; }
3583 bool MustDeselect(
Item item)
3585 if (item ==
null) {
return false; }
3586 if (!CanInteractWith(item)) {
return true; }
3587 bool hasSelectableComponent =
false;
3591 if (component.CanBeSelected && component.HasRequiredItems(
this, addMessage:
false))
3593 hasSelectableComponent =
true;
3597 return !hasSelectableComponent;
3601 partial
void UpdateControlled(
float deltaTime,
Camera cam);
3603 partial
void UpdateProjSpecific(
float deltaTime,
Camera cam);
3605 partial
void SetOrderProjSpecific(
Order order);
3610 Attacker attacker = lastAttackers.FirstOrDefault(a => a.Character == character);
3611 if (attacker !=
null)
3613 lastAttackers.Remove(attacker);
3620 if (lastAttackers.Count > maxLastAttackerCount)
3622 lastAttackers.RemoveRange(0, lastAttackers.Count - maxLastAttackerCount);
3625 attacker.
Damage += damage;
3626 lastAttackers.Add(attacker);
3632 if ((index = lastAttackers.FindIndex(a => a.Character == character)) >= 0)
3634 lastAttackers.RemoveAt(index);
3640 if (otherCharacter ==
null) {
return 0; }
3642 Attacker attacker = LastAttackers.LastOrDefault(a => a.Character == otherCharacter);
3643 if (attacker !=
null)
3650 private void UpdateAttackers(
float deltaTime)
3653 foreach (Attacker enemy
in LastAttackers)
3655 float cumulativeDamage = enemy.Damage;
3656 if (cumulativeDamage > 0)
3658 float reduction = deltaTime;
3659 if (cumulativeDamage < 2)
3664 enemy.Damage = Math.Max(0.0f, enemy.Damage - reduction);
3669 private void UpdateOxygen(
float deltaTime)
3673 if (Timing.TotalTime > pressureProtectionLastSet + 0.1)
3675 pressureProtection = 0.0f;
3680 float waterAvailable = 100;
3681 if (!AnimController.InWater && CurrentHull !=
null)
3685 OxygenAvailable += MathHelper.Clamp(waterAvailable - oxygenAvailable, -deltaTime * 50.0f, deltaTime * 50.0f);
3689 float hullAvailableOxygen = 0.0f;
3690 if (!AnimController.HeadInWater && AnimController.CurrentHull !=
null)
3694 if (OxygenAvailable * 0.98f < AnimController.CurrentHull.OxygenPercentage && UseHullOxygen)
3696 AnimController.CurrentHull.Oxygen -= Hull.OxygenConsumptionSpeed * deltaTime;
3698 hullAvailableOxygen = AnimController.CurrentHull.OxygenPercentage;
3701 OxygenAvailable += MathHelper.Clamp(hullAvailableOxygen - oxygenAvailable, -deltaTime * 50.0f, deltaTime * 50.0f);
3703 UseHullOxygen =
true;
3711 return (
float)Math.Sqrt(GetDistanceSqrToClosestPlayer());
3719 float distSqr =
float.MaxValue;
3720 foreach (
Character otherCharacter
in CharacterList)
3722 if (otherCharacter ==
this || !otherCharacter.
IsRemotePlayer) {
continue; }
3723 distSqr = Math.Min(distSqr, Vector2.DistanceSquared(otherCharacter.
WorldPosition, WorldPosition));
3730 for (
int i = 0; i <
GameMain.Server.ConnectedClients.Count; i++)
3732 var spectatePos =
GameMain.Server.ConnectedClients[i].SpectatePos;
3733 if (spectatePos !=
null)
3735 distSqr = Math.Min(distSqr, Vector2.DistanceSquared(spectatePos.Value, WorldPosition));
3739 if (
this == Controlled) {
return 0.0f; }
3740 if (controlled !=
null)
3742 distSqr = Math.Min(distSqr, Vector2.DistanceSquared(Controlled.WorldPosition, WorldPosition));
3751 float closestDist =
float.MaxValue;
3755 float dist = Vector2.Distance(simPos, limb.
SimPosition);
3757 closestDist = Math.Min(closestDist, dist);
3758 if (closestDist <= 0.0f) {
return 0.0f; }
3763 private float despawnTimer;
3764 private void UpdateDespawn(
float deltaTime,
bool createNetworkEvents =
true)
3766 if (!EnableDespawn) {
return; }
3769 if (GameMain.NetworkMember !=
null && !GameMain.NetworkMember.IsServer) {
return; }
3771 if (!IsDead || (CauseOfDeath?.Type ==
CauseOfDeathType.Disconnected && GameMain.GameSession?.Campaign !=
null)) {
return; }
3773 int subCorpseCount = 0;
3775 if (Submarine !=
null)
3777 subCorpseCount = CharacterList.Count(c => c.
IsDead && c.
Submarine == Submarine);
3778 if (subCorpseCount < GameSettings.CurrentConfig.CorpsesPerSubDespawnThreshold) {
return; }
3781 if (SelectedBy !=
null)
3783 despawnTimer = 0.0f;
3787 float distToClosestPlayer = GetDistanceToClosestPlayer();
3788 if (distToClosestPlayer > Params.DisableDistance)
3791 despawnTimer = Math.Max(despawnTimer, GameSettings.CurrentConfig.CorpseDespawnDelay - 60.0f);
3794 float despawnPriority = 1.0f;
3795 if (subCorpseCount > GameSettings.CurrentConfig.CorpsesPerSubDespawnThreshold)
3798 despawnPriority += (subCorpseCount - GameSettings.CurrentConfig.CorpsesPerSubDespawnThreshold) / (
float)GameSettings.CurrentConfig.CorpsesPerSubDespawnThreshold;
3800 if (AIController is EnemyAIController)
3803 despawnPriority *= 2.0f;
3806 despawnTimer += deltaTime * despawnPriority;
3807 if (despawnTimer < GameSettings.CurrentConfig.CorpseDespawnDelay) {
return; }
3812 private void Despawn(
bool createNetworkEvents =
true)
3814 if (!EnableDespawn) {
return; }
3816 Identifier despawnContainerId =
3818 Tags.DespawnContainer :
3819 Params.DespawnContainer;
3820 if (!despawnContainerId.IsEmpty)
3822 var containerPrefab =
3823 MapEntityPrefab.FindByIdentifier(despawnContainerId) as ItemPrefab ??
3824 ItemPrefab.Prefabs.Find(me => me?.Tags !=
null && me.Tags.Contains(despawnContainerId)) ??
3825 (MapEntityPrefab.FindByIdentifier(
"metalcrate".ToIdentifier()) as ItemPrefab);
3826 if (containerPrefab ==
null)
3828 DebugConsole.NewMessage($
"Could not spawn a container for a despawned character's items. No item with the tag \"{despawnContainerId}\" or the identifier \"metalcrate\" found.", Color.Red);
3832 Spawner?.AddItemToSpawnQueue(containerPrefab, WorldPosition, onSpawned: onItemContainerSpawned);
3835 void onItemContainerSpawned(Item item)
3837 if (Inventory ==
null) {
return; }
3839 item.UpdateTransform();
3840 item.AddTag(
"name:" + Name);
3841 if (info?.Job !=
null) { item.AddTag($
"job:{info.Job.Name}"); }
3844 if (itemContainer ==
null) {
return; }
3845 List<Item> inventoryItems =
new List<Item>(Inventory.AllItemsMod);
3848 var geneticMaterials = Inventory.FindAllItems(it => it.GetComponent<
GeneticMaterial>() !=
null, recursive:
true);
3849 foreach (var geneticMaterial
in geneticMaterials)
3851 geneticMaterial.ApplyStatusEffects(
ActionType.OnSevered, 1.0f,
this);
3854 foreach (Item inventoryItem
in inventoryItems)
3856 if (!itemContainer.Inventory.TryPutItem(inventoryItem, user:
null, createNetworkEvent: createNetworkEvents))
3859 inventoryItem.Drop(dropper:
this, createNetworkEvent: createNetworkEvents);
3863 Spawner.AddEntityToRemoveQueue(
this);
3868 Spawner.AddEntityToRemoveQueue(
this);
3874 Despawn(createNetworkEvents);
3876 for (
int i = 0; i < 2; i++)
3878 Spawner.Update(createNetworkEvents);
3884 if (CharacterList ==
null) {
return; }
3885 List<Character> list =
new List<Character>(CharacterList);
3888 if (character.
Prefab == prefab)
3895 private readonly
float maxAIRange = 20000;
3896 private readonly
float aiTargetChangeSpeed = 5;
3898 private void UpdateSightRange(
float deltaTime)
3900 if (aiTarget ==
null) {
return; }
3901 float minRange = Math.Clamp((
float)Math.Sqrt(Mass) * Visibility, 250, 1000);
3902 float massFactor = (float)Math.Sqrt(Mass / 20);
3903 float targetRange = Math.Min(minRange + massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Visibility, maxAIRange);
3904 targetRange *= 1.0f + GetStatValue(StatTypes.SightRangeMultiplier);
3905 float newRange = MathHelper.SmoothStep(aiTarget.SightRange, targetRange, deltaTime * aiTargetChangeSpeed);
3906 if (!
float.IsNaN(newRange))
3908 aiTarget.SightRange = newRange;
3912 private void UpdateSoundRange(
float deltaTime)
3914 const float textChatVolumeMultiplier = 0.5f;
3915 const float voiceChatVolumeMultiplier = 1.5f;
3917 if (aiTarget ==
null) {
return; }
3920 aiTarget.SoundRange = 0;
3924 float massFactor = (float)Math.Sqrt(Mass / 10);
3925 float targetRange = Math.Min(massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Noise, maxAIRange);
3926 float speechImpedimentMultiplier = 1.0f - SpeechImpediment / 100.0f;
3927 if (TextChatVolume > 0)
3929 targetRange = Math.Max(targetRange, TextChatVolume * textChatVolumeMultiplier *
ChatMessage.
SpeakRange * speechImpedimentMultiplier);
3934 float voipAmplitude = 0.0f;
3936 foreach (var c
in GameMain.Server.ConnectedClients)
3938 if (c.Character !=
this) {
continue; }
3939 voipAmplitude = c.VoipServerDecoder.Amplitude;
3942 #elif CLIENT && DEBUG
3943 if (Controlled ==
this && GameMain.Client !=
null)
3945 voipAmplitude = GameMain.Client.DebugServerVoipAmplitude;
3948 targetRange = Math.Max(targetRange, voipAmplitude * voiceChatVolumeMultiplier *
ChatMessage.
SpeakRange * speechImpedimentMultiplier);
3951 targetRange *= 1.0f + GetStatValue(
StatTypes.SoundRangeMultiplier);
3952 targetRange = Math.Min(targetRange, maxAIRange);
3954 float newRange = MathHelper.SmoothStep(aiTarget.SoundRange, targetRange, deltaTime * aiTargetChangeSpeed);
3956 newRange *= 1.0f + GetStatValue(
StatTypes.SoundRangeMultiplier);
3957 if (!
float.IsNaN(newRange))
3959 aiTarget.SoundRange = newRange;
3966 if (speaker ==
null || speaker.
SpeechImpediment > 100.0f) {
return false; }
3967 if (speaker ==
this) {
return true; }
3975 public void SetOrder(
Order order,
bool isNewOrder,
bool speak =
true,
bool force =
false)
3979 if (!force && orderGiver !=
null && !CanHearCharacter(orderGiver)) {
return; }
3989 foreach (var character
in CharacterList)
3991 if (character ==
this) {
continue; }
3992 if (character.TeamID != TeamID) {
continue; }
3995 foreach (var currentOrder
in character.CurrentOrders)
3997 if (currentOrder ==
null) {
continue; }
3998 if (currentOrder.Category !=
OrderCategory.Operate) {
continue; }
3999 if (currentOrder.Identifier != order.
Identifier) {
continue; }
4000 if (currentOrder.TargetEntity != order.
TargetEntity) {
continue; }
4001 if (!currentOrder.AutoDismiss) {
continue; }
4002 character.SetOrder(currentOrder.GetDismissal(), isNewOrder, speak: speak, force: force);
4009 Order orderToReplace =
null;
4010 foreach (var currentOrder
in CurrentOrders)
4012 if (currentOrder ==
null) {
continue; }
4013 if (currentOrder.Category !=
OrderCategory.Movement) {
continue; }
4014 orderToReplace = currentOrder;
4017 if (orderToReplace is { AutoDismiss:
true })
4019 SetOrder(orderToReplace.GetDismissal(), isNewOrder, speak: speak, force: force);
4027 RemoveDuplicateOrders(order);
4028 AddCurrentOrder(order);
4030 if (orderGiver !=
null && order.
Identifier !=
"dismissed" && isNewOrder)
4033 orderGiver.CheckTalents(
AbilityEffectType.OnGiveOrder, abilityOrderedCharacter);
4046 SetOrderProjSpecific(order);
4049 private void AddCurrentOrder(
Order newOrder)
4051 if (newOrder ==
null || newOrder.
Identifier ==
"dismissed")
4053 if (newOrder.
Option != Identifier.Empty)
4055 if (CurrentOrders.Any(o => o.MatchesDismissedOrder(newOrder.
Option)))
4057 var dismissedOrderInfo = CurrentOrders.First(o => o.MatchesDismissedOrder(newOrder.
Option));
4058 int dismissedOrderPriority = dismissedOrderInfo.ManualPriority;
4059 CurrentOrders.Remove(dismissedOrderInfo);
4060 for (
int i = 0; i < CurrentOrders.Count; i++)
4062 var orderInfo = CurrentOrders[i];
4063 if (orderInfo.ManualPriority < dismissedOrderPriority)
4065 CurrentOrders[i] = orderInfo.WithManualPriority(orderInfo.ManualPriority + 1);
4072 CurrentOrders.Clear();
4077 for (
int i = 0; i < CurrentOrders.Count; i++)
4079 var orderInfo = CurrentOrders[i];
4082 CurrentOrders[i] = orderInfo.WithManualPriority(orderInfo.ManualPriority - 1);
4085 CurrentOrders.RemoveAll(order => order.ManualPriority <= 0);
4086 CurrentOrders.Add(newOrder);
4088 CurrentOrders.Sort((x, y) => y.ManualPriority.CompareTo(x.ManualPriority));
4092 private bool RemoveDuplicateOrders(Order order)
4094 bool removed =
false;
4095 int? priorityOfRemoved =
null;
4096 for (
int i = CurrentOrders.Count - 1; i >= 0; i--)
4098 var orderInfo = CurrentOrders[i];
4099 if (order.Identifier == orderInfo.Identifier)
4101 priorityOfRemoved = orderInfo.ManualPriority;
4102 CurrentOrders.RemoveAt(i);
4108 if (!priorityOfRemoved.HasValue) {
return removed; }
4110 for (
int i = 0; i < CurrentOrders.Count; i++)
4112 var orderInfo = CurrentOrders[i];
4113 if (orderInfo.ManualPriority < priorityOfRemoved.Value)
4115 CurrentOrders[i] = orderInfo.WithManualPriority(orderInfo.ManualPriority + 1);
4119 CurrentOrders.RemoveAll(order => order.ManualPriority <= 0);
4121 CurrentOrders.Sort((x, y) => y.ManualPriority.CompareTo(x.ManualPriority));
4128 return GetCurrentOrder(orderInfo =>
4130 if (orderInfo ==
null) {
return false; }
4131 if (orderInfo.Identifier ==
"dismissed") { return false; }
4132 if (orderInfo.ManualPriority < 1) { return false; }
4139 return GetCurrentOrder(orderInfo =>
4141 return orderInfo.MatchesOrder(order);
4145 private Order GetCurrentOrder(Func<Order, bool> predicate)
4147 if (CurrentOrders !=
null && CurrentOrders.Any(predicate))
4149 return CurrentOrders.First(predicate);
4157 private readonly List<AIChatMessage> aiChatMessageQueue =
new List<AIChatMessage>();
4160 private readonly Dictionary<Identifier, float> prevAiChatMessages =
new Dictionary<Identifier, float>();
4164 if (identifier != Identifier.Empty)
4166 prevAiChatMessages[identifier] = (float)Timing.TotalTime;
4172 DisableLine(identifier.ToIdentifier());
4175 public void Speak(
string message,
ChatMessageType? messageType =
null,
float delay = 0.0f, Identifier identifier =
default,
float minDurationBetweenSimilar = 0.0f)
4178 if (
string.IsNullOrEmpty(message)) {
return; }
4180 if (SpeechImpediment >= 100.0f) {
return; }
4182 if (prevAiChatMessages.ContainsKey(identifier) &&
4183 prevAiChatMessages[identifier] < Timing.TotalTime - minDurationBetweenSimilar)
4185 prevAiChatMessages.Remove(identifier);
4189 if (identifier != Identifier.Empty && minDurationBetweenSimilar > 0.0f &&
4190 (aiChatMessageQueue.Any(m => m.Identifier == identifier) || prevAiChatMessages.ContainsKey(identifier)))
4194 aiChatMessageQueue.Add(
new AIChatMessage(message, messageType, identifier, delay));
4197 private void UpdateAIChatMessages(
float deltaTime)
4201 List<AIChatMessage> sentMessages =
new List<AIChatMessage>();
4202 foreach (AIChatMessage message
in aiChatMessageQueue)
4204 message.SendDelay -= deltaTime;
4205 if (message.SendDelay > 0.0f) {
continue; }
4208 if (message.MessageType ==
null)
4213 if (GameMain.GameSession?.CrewManager !=
null && GameMain.GameSession.CrewManager.IsSinglePlayer)
4216 if (!
string.IsNullOrEmpty(modifiedMessage))
4218 GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(Name, modifiedMessage, message.MessageType.Value,
this);
4222 Signal s =
new Signal(modifiedMessage, sender:
this, source: radio.Item);
4223 radio.TransmitSignal(s, sentFromChat:
true);
4228 if (GameMain.Server !=
null && message.MessageType !=
ChatMessageType.Order)
4230 GameMain.Server.SendChatMessage(message.Message, message.MessageType.Value,
null,
this);
4236 sentMessages.Add(message);
4239 foreach (AIChatMessage sent
in sentMessages)
4241 sent.SendTime = Timing.TotalTime;
4242 aiChatMessageQueue.Remove(sent);
4243 if (sent.Identifier != Identifier.Empty)
4245 prevAiChatMessages[sent.Identifier] = (float)sent.SendTime;
4249 if (prevAiChatMessages.Count > 100)
4251 HashSet<Identifier> toRemove =
new HashSet<Identifier>();
4252 foreach (KeyValuePair<Identifier,float> prevMessage
in prevAiChatMessages)
4254 if (prevMessage.Value < Timing.TotalTime - 60.0f)
4256 toRemove.Add(prevMessage.Key);
4259 foreach (Identifier identifier
in toRemove)
4261 prevAiChatMessages.Remove(identifier);
4266 public void SetAllDamage(
float damageAmount,
float bleedingDamageAmount,
float burnDamageAmount)
4273 return ApplyAttack(attacker, worldPosition, attack, deltaTime, impulseDirection, playSound);
4283 string errorMsg =
"Tried to apply an attack to a removed character ([name]).\n" + Environment.StackTrace.CleanupStackTrace();
4284 DebugConsole.ThrowError(errorMsg.Replace(
"[name]", Name));
4285 GameAnalyticsManager.AddErrorEventOnce(
"Character.ApplyAttack:RemovedCharacter", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace(
"[name]", SpeciesName.Value));
4289 Limb limbHit = targetLimb;
4293 Vector2 attackImpulse = Vector2.Zero;
4294 if (Math.Abs(impulseMagnitude) > 0.0f)
4296 impulseDirection = impulseDirection.LengthSquared() > 0.0001f ?
4297 Vector2.Normalize(impulseDirection) :
4299 attackImpulse = impulseDirection * impulseMagnitude;
4303 IEnumerable<Affliction> attackAfflictions;
4313 var attackResult = targetLimb ==
null ?
4317 if (attacker !=
null)
4326 if (attacker !=
null)
4332 if (limbHit != mainLimb)
4335 mainLimb.
body?.
ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
4340 StringBuilder sb =
new StringBuilder();
4341 sb.Append(GameServer.CharacterLogName(
this) +
" attacked by " + GameServer.CharacterLogName(attackingCharacter) +
".");
4342 if (attackResult.Afflictions !=
null)
4344 foreach (
Affliction affliction
in attackResult.Afflictions)
4346 if (Math.Abs(affliction.
Strength) <= 0.1f) {
continue;}
4347 sb.Append($
" {affliction.Prefab.Name}: {affliction.Strength.ToString("0.0
")}");
4356 return attackResult;
4359 public void TrySeverLimbJoints(
Limb targetLimb,
float severLimbsProbability,
float damage,
bool allowBeheading,
bool ignoreSeveranceProbabilityModifier =
false,
Character attacker =
null)
4365 DebugConsole.ThrowError($
"{Name} is attempting to sever joints of {targetLimb.character.Name}!");
4369 if (damage > 0 && damage < targetLimb.
Params.MinSeveranceDamage) {
return; }
4372 if (!allowBeheading && targetLimb.
type ==
LimbType.Head) {
return; }
4375 bool wasSevered =
false;
4376 float random = Rand.Value();
4384 var referenceLimb = targetLimb.type ==
LimbType.Head && targetLimb.Params.ID == 0 ? joint.
LimbA : joint.LimbB;
4385 if (referenceLimb != targetLimb) {
continue; }
4386 float probability = severLimbsProbability;
4387 if (!IsDead && !ignoreSeveranceProbabilityModifier)
4389 probability *= joint.
Params.SeveranceProbabilityModifier;
4391 if (probability <= 0) {
continue; }
4392 if (random > probability) {
continue; }
4396 wasSevered = severed;
4400 Limb otherLimb = joint.
LimbA == targetLimb ? joint.LimbB : joint.
LimbA;
4401 otherLimb.
body.
ApplyLinearImpulse(targetLimb.LinearVelocity * targetLimb.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
4402 if (attacker !=
null)
4404 if (statusEffects.TryGetValue(
ActionType.OnSevered, out var statusEffectList))
4406 foreach (var statusEffect
in statusEffectList)
4408 statusEffect.SetUser(attacker);
4411 if (targetLimb.StatusEffects.TryGetValue(
ActionType.OnSevered, out var limbStatusEffectList))
4413 foreach (var statusEffect
in limbStatusEffectList)
4415 statusEffect.SetUser(attacker);
4419 ApplyStatusEffects(
ActionType.OnSevered, 1.0f);
4420 targetLimb.ApplyStatusEffects(
ActionType.OnSevered, 1.0f);
4423 if (wasSevered && targetLimb.character.AIController is
EnemyAIController enemyAI)
4425 enemyAI.ReevaluateAttacks();
4429 public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions,
float stun,
bool playSound, Vector2? attackImpulse =
null,
Character attacker =
null,
float damageMultiplier = 1f)
4431 return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse ?? Vector2.Zero, out _, attacker, damageMultiplier: damageMultiplier);
4434 public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions,
float stun,
bool playSound, Vector2 attackImpulse, out
Limb hitLimb,
Character attacker =
null,
float damageMultiplier = 1)
4440 float closestDistance = 0.0f;
4443 float distance = Vector2.DistanceSquared(worldPosition, limb.
WorldPosition);
4444 if (hitLimb ==
null || distance < closestDistance)
4447 closestDistance = distance;
4451 return DamageLimb(worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier);
4457 foreach (
Character attackerCrewmember
in GetFriendlyCrew(
this))
4463 if (!IsOnPlayerTeam) {
return; }
4464 CreatureMetrics.RecordKill(target.SpeciesName);
4467 public 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)
4492 if (attacker.TeamID == TeamID)
4494 afflictions = afflictions.Where(a => a.Prefab.IsBuff);
4500 if (attackImpulse.LengthSquared() > 0.0f)
4503 if (diff == Vector2.Zero) { diff = Rand.Vector(1.0f); }
4504 Vector2 hitPos = hitLimb.
SimPosition + ConvertUnits.ToSimUnits(diff);
4505 hitLimb.
body.
ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
4507 if (hitLimb != mainLimb)
4513 bool wasDead = IsDead;
4514 Vector2 simPos = hitLimb.
SimPosition + ConvertUnits.ToSimUnits(dir);
4516 AttackResult attackResult = hitLimb.
AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration, attacker: attacker);
4524 if (attacker !=
this)
4527 OnAttackedProjSpecific(attacker, attackResult, stun);
4530 TryAdjustAttackerSkill(attacker, attackResult);
4533 if (attackResult.
Damage > 0)
4535 LastDamage = attackResult;
4536 if (attacker !=
null && attacker !=
this && !attacker.Removed)
4538 AddAttacker(attacker, attackResult.
Damage);
4541 CreatureMetrics.AddEncounter(attacker.SpeciesName);
4543 if (attacker.IsOnPlayerTeam)
4545 CreatureMetrics.AddEncounter(SpeciesName);
4548 ApplyStatusEffects(
ActionType.OnDamaged, 1.0f);
4552 if (Params.UseBossHealthBar && Controlled !=
null && Controlled.teamID == attacker?.teamID)
4557 return attackResult;
4564 if (attacker ==
null) {
return; }
4567 if (!isEnemy) {
return; }
4568 float weaponDamage = 0;
4569 float medicalDamage = 0;
4570 foreach (var affliction
in attackResult.Afflictions)
4572 if (affliction.Prefab.IsBuff) {
continue; }
4573 if (Params.IsMachine && !affliction.Prefab.AffectMachines) {
continue; }
4574 if (Params.Health.ImmunityIdentifiers.Contains(affliction.Identifier)) {
continue; }
4577 if (!Params.Health.PoisonImmunity)
4579 float relativeVitality = MaxVitality / 100f;
4581 float dmg = affliction.Strength;
4582 if (relativeVitality > 0)
4584 dmg /= relativeVitality;
4586 if (PoisonVulnerability > 0)
4588 dmg /= PoisonVulnerability;
4590 float strength = MaxVitality;
4591 if (Params.AI !=
null)
4593 strength = Params.AI.CombatStrength;
4596 float vitalityFactor = MathHelper.Lerp(0.5f, 2f, MathUtils.InverseLerp(0, 1000, strength));
4597 dmg *= vitalityFactor;
4598 medicalDamage += dmg * affliction.Prefab.MedicalSkillGain;
4603 medicalDamage += affliction.GetVitalityDecrease(
null) * affliction.Prefab.MedicalSkillGain;
4605 weaponDamage += affliction.GetVitalityDecrease(
null) * affliction.Prefab.WeaponsSkillGain;
4607 if (medicalDamage > 0)
4609 IncreaseSkillLevel(Tags.MedicalSkill, medicalDamage);
4611 if (weaponDamage > 0)
4613 IncreaseSkillLevel(Tags.WeaponsSkill, weaponDamage);
4616 void IncreaseSkillLevel(Identifier skill,
float damage)
4624 if (healer ==
null) {
return; }
4626 if (isEnemy) {
return; }
4627 float medicalGain = healthChange;
4628 if (affliction?.
Prefab is { IsBuff:
true } && (!Params.IsMachine || affliction.Prefab.AffectMachines))
4630 medicalGain += affliction.Strength * affliction.Prefab.MedicalSkillGain;
4632 if (medicalGain > 0)
4644 public void SetStun(
float newStun,
bool allowStunDecrease =
false,
bool isNetworkMessage =
false)
4648 if (newStun > 0 && Params.Health.StunImmunity)
4655 if ((newStun <= Stun && !allowStunDecrease) || !MathUtils.IsValid(newStun)) {
return; }
4656 if (Math.Sign(newStun) != Math.Sign(Stun))
4663 SelectedItem = SelectedSecondaryItem =
null;
4664 if (SelectedCharacter !=
null) { DeselectCharacter(); }
4666 HealthUpdateInterval = 0.0f;
4669 private readonly List<ISerializableEntity> targets =
new List<ISerializableEntity>();
4674 float eatingRegen = Params.Health.HealthRegenerationWhenEating;
4675 if (eatingRegen > 0)
4680 if (statusEffects.TryGetValue(actionType, out var statusEffectList))
4682 foreach (
StatusEffect statusEffect
in statusEffectList)
4689 if (LastAttacker ==
null || !LastAttacker.
IsPlayer)
4700 statusEffect.
Apply(actionType, deltaTime,
this, targets);
4704 foreach (var limbType
in statusEffect.
targetLimbs)
4711 if (limb.IsSevered) {
continue; }
4712 if (limb.type == limbType)
4714 ApplyToLimb(actionType, deltaTime, statusEffect,
this, limb);
4724 ApplyToLimb(actionType, deltaTime, statusEffect,
this, limb);
4733 ApplyToLimb(actionType, deltaTime, statusEffect,
this, limb);
4743 if (limb.IsSevered) {
continue; }
4744 ApplyToLimb(actionType, deltaTime, statusEffect, character:
this, limb);
4749 statusEffect.
Apply(actionType, deltaTime,
this,
this);
4753 statusEffect.
Apply(actionType, deltaTime,
this, CurrentHull);
4774 statusEffect.
Apply(actionType, deltaTime, entity: character, target: limb);
4778 private void Implode(
bool isNetworkMessage =
false)
4782 if (!isNetworkMessage)
4784 if (GameMain.NetworkMember is { IsClient: true }) {
return; }
4787 CharacterHealth.ApplyAffliction(
null,
new Affliction(AfflictionPrefab.Pressure, AfflictionPrefab.Pressure.MaxStrength));
4788 if (GameMain.NetworkMember is not { IsClient: true } || isNetworkMessage)
4808 if (!MathUtils.IsValid(diff))
4810 string errorMsg =
"Attempted to apply an invalid impulse to a limb in Character.BreakJoints (" + diff +
"). Limb position: " + limb.
SimPosition +
", center of mass: " + centerOfMass +
".";
4811 DebugConsole.ThrowError(errorMsg);
4812 GameAnalyticsManager.AddErrorEventOnce(
"Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
4816 if (diff == Vector2.Zero) {
continue; }
4824 if (joint.LimbA.type ==
LimbType.Head || joint.LimbB.type ==
LimbType.Head) {
continue; }
4825 if (joint.revoluteJoint !=
null)
4827 joint.revoluteJoint.LimitEnabled =
false;
4832 partial
void ImplodeFX();
4838 HealthUpdateInterval = 0.0f;
4860 if (item.Equipper is { IsPlayer: true } &&
4861 item.GetComponents<
ItemContainer>().Any(ic => ic.BlameEquipperForDeath()))
4863 killer = item.Equipper;
4874 causeOfDeath, causeOfDeathAffliction?.
Prefab,
4875 killer, LastDamageSource);
4882 info.LastResistanceMultiplierSkillLossDeath = GetAbilityResistance(Tags.SkillLossDeathResistance);
4883 info.LastResistanceMultiplierSkillLossRespawn = GetAbilityResistance(Tags.SkillLossRespawnResistance);
4889 ApplyStatusEffects(
ActionType.OnDeath, 1.0f);
4898 characterInfo.PermanentlyDead =
true;
4903 if (Info is not
null)
4905 Info.LastRewardDistribution = Option.Some(Wallet.RewardDistribution);
4909 if (GameAnalyticsManager.SendUserStatistics &&
Prefab?.
ContentPackage == ContentPackageManager.VanillaCorePackage)
4911 string causeOfDeathStr = causeOfDeathAffliction ==
null ?
4914 string characterType = GetCharacterType(
this);
4915 GameAnalyticsManager.AddDesignEvent(
"Kill:" + characterType +
":" + causeOfDeathStr);
4918 GameAnalyticsManager.AddDesignEvent(
"Kill:" + characterType +
":Killer:" + GetCharacterType(
CauseOfDeath.
Killer));
4924 GameAnalyticsManager.AddDesignEvent(
"Kill:" + characterType +
":DamageSource:" + damageSourceStr);
4927 static string GetCharacterType(
Character character)
4934 return "EnemyHuman";
4938 return "FriendlyNPC";
4954 AchievementManager.OnCharacterKilled(
this,
CauseOfDeath);
4958 KillProjSpecific(causeOfDeath, causeOfDeathAffliction, log);
4963 info.MissionsCompletedSinceDeath = 0;
4970 foreach (
Item heldItem
in HeldItems.ToList())
4973 var wearable = heldItem.GetComponent<
Wearable>();
4974 if (wearable is { IsActive:
true }) {
continue; }
4975 heldItem.
Drop(
this);
4979 SelectedItem = SelectedSecondaryItem =
null;
4980 SelectedCharacter =
null;
4987 if (joint.revoluteJoint !=
null)
4989 joint.revoluteJoint.MotorEnabled =
false;
4997 public void Revive(
bool removeAfflictions =
true,
bool createNetworkEvent =
false)
5001 DebugConsole.ThrowError(
"Attempting to revive an already removed character\n" + Environment.StackTrace.CleanupStackTrace());
5008 if (removeAfflictions)
5011 SetAllDamage(0.0f, 0.0f, 0.0f);
5013 SetStun(0.0f,
true);
5020 info.CauseOfDeath =
null;
5028 info.PermanentlyDead =
false;
5035 if (revoluteJoint !=
null)
5037 revoluteJoint.MotorEnabled =
true;
5066 DebugConsole.ThrowError(
"Attempting to remove an already removed character\n" + Environment.StackTrace.CleanupStackTrace());
5069 DebugConsole.Log(
"Removing character " + Name +
" (ID: " + ID +
")");
5081 foreach (
Item heldItem
in HeldItems.ToList())
5083 heldItem.
Drop(
this);
5091 if (Controlled ==
this) { Controlled =
null; }
5094 CharacterList.Remove(
this);
5096 foreach (var attachedProjectile
in AttachedProjectiles.ToList())
5098 attachedProjectile.Unstick();
5100 Latchers.ForEachMod(l => l?.DeattachFromBody(reset:
true));
5107 Spawner?.AddItemToRemoveQueue(item);
5111 itemSelectedDurations.Clear();
5113 DisposeProjSpecific();
5125 partial
void DisposeProjSpecific();
5137 humanAI.PathSteering?.ResetPath();
5143 if (inventory ==
null || parentElement ==
null) {
return; }
5144 var items = inventory.
AllItems.Distinct();
5145 foreach (
Item item
in items)
5148 var itemElement = item.
Save(parentElement);
5150 List<int> slotIndices = inventory.FindIndices(item);
5151 itemElement.Add(
new XAttribute(
"i",
string.Join(
",", slotIndices)));
5155 XElement childInvElement =
new XElement(
"inventory");
5156 itemElement.Add(childInvElement);
5157 SaveInventory(container.
Inventory, childInvElement);
5167 SaveInventory(
Inventory, Info?.InventoryData);
5172 SpawnInventoryItemsRecursive(inventory, itemData,
new List<Item>());
5175 private void SpawnInventoryItemsRecursive(
Inventory inventory,
ContentXElement element, List<Item> extraDuffelBags)
5177 foreach (var itemElement
in element.
Elements())
5180 if (newItem ==
null) {
continue; }
5182 if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
5183 GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsServer)
5188 newItem.GetComponent<
Terminal>()?.SyncHistory();
5192 int[] slotIndices = itemElement.GetAttributeIntArray(
"i",
new int[] { 0 });
5193 if (!slotIndices.Any())
5195 DebugConsole.ThrowError(
"Invalid inventory data in character \"" + Name +
"\" - no slot indices found");
5199 bool canBePutInOriginalInventory =
true;
5200 if (slotIndices[0] >= inventory.
Capacity)
5202 canBePutInOriginalInventory =
false;
5205 for (
int i = 0; i < inventory.
Capacity; i++)
5210 canBePutInOriginalInventory =
true;
5217 canBePutInOriginalInventory = inventory.
CanBePutInSlot(newItem, slotIndices[0], ignoreCondition:
true);
5220 if (canBePutInOriginalInventory)
5222 inventory.
TryPutItem(newItem, slotIndices[0],
false,
false,
null);
5223 newItem.ParentInventory = inventory;
5228 for (
int i = 0; i < inventory.
Capacity; i++)
5230 if (slotIndices.Contains(i))
5234 else if (inventory.
FindIndices(newItem).Contains(i))
5244 if (extraDuffelBags.None(i => i.OwnInventory.CanBePut(newItem)) && ItemPrefab.FindByIdentifier(
"duffelbag".ToIdentifier()) is ItemPrefab duffelBagPrefab)
5246 var hull = Hull.FindHull(WorldPosition, guess: CurrentHull);
5247 var mainSub =
Submarine.MainSubs.FirstOrDefault(s => s.TeamID == TeamID);
5248 if ((hull ==
null || hull.Submarine != mainSub) && mainSub !=
null)
5250 var wp = WayPoint.GetRandom(spawnType:
SpawnType.Cargo, sub: mainSub) ?? WayPoint.GetRandom(sub: mainSub);
5253 hull = Hull.FindHull(wp.WorldPosition);
5256 var newDuffelBag =
new Item(duffelBagPrefab,
5257 hull !=
null ? CargoManager.GetCargoPos(hull, duffelBagPrefab) : Position,
5258 hull?.Submarine ?? Submarine);
5259 extraDuffelBags.Add(newDuffelBag);
5261 Spawner.CreateNetworkEvent(
new EntitySpawner.SpawnEntity(newDuffelBag));
5266 for (
int i = 0; i < extraDuffelBags.Count; i++)
5268 var duffelBag = extraDuffelBags[i];
5269 for (
int j = 0; j < duffelBag.OwnInventory.Capacity; j++)
5271 if (duffelBag.OwnInventory.TryPutItem(newItem, j,
false,
false,
null))
5273 newItem.ParentInventory = duffelBag.OwnInventory;
5281 foreach (var circuitBox
in newItem.GetComponents<CircuitBox>())
5283 circuitBox.MarkServerRequiredInitialization();
5287 int itemContainerIndex = 0;
5288 var itemContainers = newItem.GetComponents<
ItemContainer>().ToList();
5289 foreach (var childInvElement
in itemElement.Elements())
5291 if (itemContainerIndex >= itemContainers.Count)
break;
5292 if (!childInvElement.Name.ToString().Equals(
"inventory", StringComparison.OrdinalIgnoreCase)) {
continue; }
5293 SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement, extraDuffelBags);
5294 itemContainerIndex++;
5299 private readonly HashSet<AttackContext> currentContexts =
new HashSet<AttackContext>();
5303 currentContexts.Clear();
5312 if (CurrentHull ==
null)
5320 return currentContexts;
5323 private readonly List<Hull> visibleHulls =
new List<Hull>();
5324 private readonly HashSet<Hull> tempList =
new HashSet<Hull>();
5333 visibleHulls.Clear();
5335 if (CurrentHull !=
null)
5337 visibleHulls.Add(CurrentHull);
5338 var adjacentHulls = CurrentHull.GetConnectedHulls(
true, 1);
5339 float maxDistance = 1000f;
5340 foreach (var hull
in adjacentHulls)
5342 if (hull.ConnectedGaps.Any(g =>
5344 g.linkedTo.Contains(CurrentHull) &&
5345 Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2)))
5347 if (Vector2.DistanceSquared(hull.WorldPosition, WorldPosition) < Math.Pow(maxDistance, 2))
5349 visibleHulls.Add(hull);
5353 visibleHulls.AddRange(CurrentHull.GetLinkedEntities(tempList, filter: h =>
5356 if (adjacentHulls.Contains(h))
5362 if (h.ConnectedGaps.Any(g =>
5364 Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2) &&
5367 return Vector2.DistanceSquared(h.WorldPosition, WorldPosition) < Math.Pow(maxDistance, 2);
5376 return visibleHulls;
5381 public bool IsCaptain => HasJob(
"captain");
5382 public bool IsEngineer => HasJob(
"engineer");
5383 public bool IsMechanic => HasJob(
"mechanic");
5384 public bool IsMedic => HasJob(
"medicaldoctor");
5385 public bool IsSecurity => HasJob(
"securityofficer") || HasJob(
"vipsecurityofficer") || HasJob(
"outpostsecurityofficer");
5386 public bool IsAssistant => HasJob(
"assistant");
5387 public bool IsWatchman => HasJob(
"watchman");
5388 public bool IsVip => HasJob(
"prisoner");
5389 public bool IsPrisoner => HasJob(
"prisoner");
5390 public bool IsKiller => HasJob(
"killer");
5392 public Color? UniqueNameColor {
get;
set; } =
null;
5394 public bool HasJob(
string identifier) => Info?.Job?.Prefab.Identifier == identifier;
5396 public bool HasJob(Identifier identifier) => Info?.Job?.Prefab.Identifier == identifier;
5403 public bool IsImmuneToPressure => !NeedsAir || HasAbilityFlag(
AbilityFlags.ImmuneToPressure);
5406 private readonly List<CharacterTalent> characterTalents =
new List<CharacterTalent>();
5408 public IReadOnlyCollection<CharacterTalent> CharacterTalents => characterTalents;
5412 List<Identifier> toBeRemoved =
null;
5413 foreach (Identifier talent
in info.UnlockedTalents)
5415 if (!GiveTalent(talent, addingFirstTime:
false))
5417 DebugConsole.AddWarning(Name +
" had talent that did not exist! Removing talent from CharacterInfo.");
5418 toBeRemoved ??=
new List<Identifier>();
5419 toBeRemoved.Add(talent);
5423 if (toBeRemoved !=
null)
5425 foreach (Identifier removeTalent
in toBeRemoved)
5427 Info.UnlockedTalents.Remove(removeTalent);
5432 public bool GiveTalent(Identifier talentIdentifier,
bool addingFirstTime =
true)
5435 if (talentPrefab ==
null)
5437 DebugConsole.AddWarning($
"Tried to add talent by identifier {talentIdentifier} to character {Name}, but no such talent exists.");
5440 return GiveTalent(talentPrefab, addingFirstTime);
5443 public bool GiveTalent(UInt32 talentIdentifier,
bool addingFirstTime =
true)
5446 if (talentPrefab ==
null)
5448 DebugConsole.AddWarning($
"Tried to add talent by identifier {talentIdentifier} to character {Name}, but no such talent exists.");
5451 return GiveTalent(talentPrefab, addingFirstTime);
5456 if (info ==
null) {
return false; }
5457 info.UnlockedTalents.Add(talentPrefab.
Identifier);
5458 if (characterTalents.Any(t => t.Prefab == talentPrefab)) {
return false; }
5463 characterTalents.Add(characterTalent);
5467 if (addingFirstTime)
5469 OnTalentGiven(talentPrefab);
5470 GameAnalyticsManager.AddDesignEvent(
"TalentUnlocked:" + (info.Job?.Prefab.Identifier ??
"None".ToIdentifier()) +
":" + talentPrefab.
Identifier,
5478 if (info ==
null) {
return false; }
5479 return info.UnlockedTalents.Contains(identifier);
5484 if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))
5486 foreach (TalentSubTree talentSubTree
in talentTree.TalentSubTrees)
5488 foreach (TalentOption talentOption
in talentSubTree.TalentOptionStages)
5490 if (!talentOption.HasMaxTalents(info.UnlockedTalents))
5502 return characterTalents.Any();
5509 characterTalent.
CheckTalent(abilityEffectType, abilityObject);
5515 foreach (var characterTalent
in characterTalents)
5517 characterTalent.
CheckTalent(abilityEffectType,
null);
5525 private readonly HashSet<Hull> sameRoomHulls =
new();
5533 if (character ==
this) {
return true; }
5535 if (character.
CurrentHull is
null || CurrentHull is
null)
5542 if (character.
CurrentHull == CurrentHull) {
return true; }
5544 sameRoomHulls.Clear();
5545 CurrentHull.GetLinkedEntities(sameRoomHulls);
5546 sameRoomHulls.Add(CurrentHull);
5548 return sameRoomHulls.Contains(character.
CurrentHull);
5553 if (character is
null)
5562 return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
5569 foreach (Identifier unlockedItem
in talent.UnlockedStoreItems)
5571 if (prefab.Tags.Contains(unlockedItem)) {
return true; }
5584 if (amount <= 0) {
return; }
5588 if (!(campaign is
MultiPlayerCampaign mpCampaign)) {
throw new InvalidOperationException(
"Campaign on a server is not a multiplayer campaign"); }
5589 Client targetClient =
null;
5595 targetClient = client;
5600 wallet = targetClient is
null ? mpCampaign.Bank : mpCampaign.GetWallet(targetClient);
5602 wallet = campaign.Wallet;
5605 int prevAmount = wallet.Balance;
5606 wallet.Give(amount);
5607 OnMoneyChanged(prevAmount, wallet.Balance);
5614 if (amount == campaign.Wallet.Balance) {
return; }
5616 int prevAmount = campaign.Wallet.Balance;
5617 campaign.Wallet.Balance = amount;
5618 OnMoneyChanged(prevAmount, campaign.Wallet.Balance);
5622 partial
void OnMoneyChanged(
int prevAmount,
int newAmount);
5628 private readonly Dictionary<StatTypes, float> statValues =
new Dictionary<StatTypes, float>();
5633 private readonly Dictionary<StatTypes, float> wearableStatValues =
new Dictionary<StatTypes, float>();
5637 if (!IsHuman) {
return 0f; }
5639 float statValue = 0f;
5640 if (statValues.TryGetValue(statType, out
float value))
5648 if (Info !=
null && includeSaved)
5651 statValue += Info.GetSavedStatValue(statType);
5653 if (wearableStatValues.TryGetValue(statType, out
float wearableValue))
5655 statValue += wearableValue;
5657 foreach (var heldItem
in HeldItems)
5662 statValue += holdableValue;
5670 wearableStatValues.Clear();
5676 foreach (var statValuePair
in wearable.WearableStatValues)
5678 if (wearableStatValues.ContainsKey(statValuePair.Key))
5680 wearableStatValues[statValuePair.Key] += statValuePair.Value;
5684 wearableStatValues.Add(statValuePair.Key, statValuePair.Value);
5693 if (statValues.ContainsKey(statType))
5695 statValues[statType] += value;
5699 statValues.Add(statType, value);
5703 private static StatTypes GetSkillStatType(Identifier skillIdentifier)
5706 switch (skillIdentifier.Value.ToLowerInvariant())
5727 abilityFlags |= abilityFlag;
5732 abilityFlags &= ~abilityFlag;
5740 private readonly Dictionary<TalentResistanceIdentifier, float> abilityResistances =
new();
5744 float resistance = 0f;
5745 bool hadResistance =
false;
5747 foreach (var (key, value) in abilityResistances)
5749 if (key.ResistanceIdentifier == resistanceId)
5751 resistance += value;
5752 hadResistance =
true;
5757 return hadResistance ? resistance : 1f;
5762 float resistance = 0f;
5763 bool hadResistance =
false;
5765 foreach (var (key, value) in abilityResistances)
5768 key.ResistanceIdentifier == affliction.
Identifier)
5770 resistance += value;
5771 hadResistance =
true;
5776 return hadResistance ? resistance : 1f;
5781 if (!MathUtils.IsValid(value))
5784 DebugConsole.ThrowError($
"Attempted to set ability resistance to an invalid value ({value})\n" + Environment.StackTrace.CleanupStackTrace());
5789 if (abilityResistances.ContainsKey(identifier))
5791 abilityResistances[identifier] *= value;
5795 abilityResistances.Add(identifier, value);
5807 if (myTeam == otherTeam) {
return true; }
5808 return myTeam
switch
5829 ReleaseSecondaryItem();
5843 public bool AggressiveBehavior {
get; }
5847 DesiredTeamId = desiredTeamId;
5848 TeamChangePriority = teamChangePriority;
5849 AggressiveBehavior = aggressiveBehavior;
5855 public Character Character {
get;
set; }
5857 public AbilityCharacterLoot(Character character)
5876 public float DamageMultiplier {
get;
set; } = 1f;
5877 public float AddedPenetration {
get;
set; } = 0f;
5878 public List<Affliction> Afflictions {
get;
set; }
5879 public bool ShouldImplode {
get;
set; } =
false;
5886 SourceAttack = sourceAttack;
5888 if (attacker !=
null)
5890 Attacker = attacker;
static float GetDistanceFactor(Vector2 selfPos, Vector2 targetWorldPos, float factorAtMaxDistance, float verticalDistanceMultiplier=3, float maxDistance=10000.0f, float factorAtMinDistance=1.0f)
Get a normalized value representing how close the target position is. The value is a rough estimation...
static List< AITarget > List
List< Affliction > Afflictions
AbilityAttackData(Attack sourceAttack, Character target, Character attacker)
AbilityAttackResult(AttackResult attackResult)
AbilityCharacterKill(Character character, Character killer)
AbilityCharacterKiller(Character character)
AbilityItemSelected(Item item)
AbilityOrderedCharacter(Character character)
ActiveTeamChange(CharacterTeamType desiredTeamId, TeamChangePriorities teamChangePriority, bool aggressiveBehavior=false)
override string ToString()
float GetSkillMultiplier()
Character Source
Which character gave this affliction
readonly AfflictionPrefab Prefab
A special affliction type that gradually makes the character turn into another type of character....
static Identifier GetHuskedSpeciesName(Identifier speciesName, AfflictionPrefabHusk prefab)
static Identifier GetNonHuskedSpeciesName(Identifier huskedSpeciesName, AfflictionPrefabHusk prefab)
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly Identifier BleedingType
static readonly Identifier DamageType
static AfflictionPrefab OrganDamage
static readonly Identifier ParalysisType
readonly Identifier AfflictionType
Arbitrary string that is used to identify the type of the affliction.
static readonly PrefabCollection< AfflictionPrefab > Prefabs
static readonly Identifier EMPType
static readonly Identifier PoisonType
AfflictionPrefabHusk is a special type of affliction that has added functionality for husk infection.
AnimationType ForceSelectAnimationType
float GetCurrentSpeed(bool useMaxSpeed)
void UpdateAnimations(float deltaTime)
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float ImpactMultiplier
Used for multiplying the physics forces.
Vector2 TargetImpulseWorld
float DamageMultiplier
Used for multiplying all the damage.
readonly Dictionary< Affliction, XElement > Afflictions
float SeverLimbsProbability
readonly Character Killer
readonly CauseOfDeathType Type
readonly Entity DamageSource
static void ShowBossHealthBar(Character character, float damage)
void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking=true, bool ignoreUnkillability=false)
float GetStatValue(StatTypes statType)
float GetLimbDamage(Limb limb, Identifier afflictionType)
void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
static CharacterHealth?? OpenHealthWindow
Affliction GetAfflictionOfType(Identifier afflictionType, bool allowLimbAfflictions=true)
void RemoveAllAfflictions()
bool HasFlag(AbilityFlags flagType)
CharacterHealth(Character character)
void Update(float deltaTime)
IEnumerable< Identifier > GetActiveAfflictionTags()
IReadOnlyCollection< Affliction > GetAllAfflictions()
void SetHealthBarVisibility(bool value)
void ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking=true)
bool WasInFullHealth
Was the character in full health at the beginning of the frame?
void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction=null, Character attacker=null)
float GetAfflictionStrengthByType(Identifier afflictionType, bool allowLimbAfflictions=true)
void ApplyAfflictionStatusEffects(ActionType type)
bool HasTeamChange(string identifier)
List< Order > CurrentOrders
bool IsKeyHit(InputType inputType)
bool RequireConsciousnessForCustomInteract
readonly CharacterParams Params
static IEnumerable< Character > GetFriendlyCrew(Character character)
IEnumerable< AttackContext > GetAttackContexts()
void SelectCharacter(Character character)
static int CharacterUpdateInterval
bool IsItemTakenBySomeoneElse(Item item)
void SetCustomInteract(Action< Character, Character > onCustomInteract, LocalizedString hudText)
Set an action that's invoked when another character interacts with this one.
static bool IsFriendly(Character me, Character other)
Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, RagdollParams ragdollParams=null, bool spawnInitialItems=true)
void SetInput(InputType inputType, bool hit, bool held)
static bool IsSameSpeciesOrGroup(Character me, Character other)
readonly HashSet< Projectile > AttachedProjectiles
bool CanInteractWith(Item item, out float distanceToItem, bool checkLinked)
float GetDamageDoneByAttacker(Character otherCharacter)
float GetSkillLevel(string skillIdentifier)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
float??? MaxPerceptionDistance
void ReloadHead(int? headId=null, int hairIndex=-1, int beardIndex=-1, int moustacheIndex=-1, int faceAttachmentIndex=-1)
readonly Dictionary< Identifier, SerializableProperty > Properties
CharacterHealth CharacterHealth
float ApplyTemporarySpeedLimits(float speed)
Order GetCurrentOrderWithTopPriority()
void AddAttacker(Character character, float damage)
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
bool IsRemotelyControlled
Is the character controlled remotely (either by another player, or a server-side AIController)
bool HasJob(string identifier)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
virtual AIController AIController
void SetOriginalTeam(CharacterTeamType newTeam)
static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Create a new character
static Character Create(Identifier speciesName, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, bool createNetworkEvent=true, RagdollParams ragdoll=null, bool throwErrorIfNotFound=true, bool spawnInitialItems=true)
const float MaxHighlightDistance
Vector2? CursorWorldPosition
delegate void OnDeathHandler(Character character, CauseOfDeath causeOfDeath)
bool DisableFocusingOnEntities
Prevents the character from highlighting items or characters with the cursor, meaning it can't intera...
CampaignMode.InteractionType CampaignInteractionType
float GetLegPenalty(float startSum=0)
Character LastOrderedCharacter
float KnockbackCooldownTimer
bool CanInteractWith(Item item, bool checkLinked=true)
bool IsInSameRoomAs(Character character)
Check if the character is in the same room Room and hull differ in that a room can consist of multipl...
void Revive(bool removeAfflictions=true, bool createNetworkEvent=false)
bool CanRunWhileDragging()
CharacterTeamType?? OriginalTeamID
bool TryRemoveTeamChange(string identifier)
AttackResult AddDamage(Vector2 worldPosition, IEnumerable< Affliction > afflictions, float stun, bool playSound, Vector2 attackImpulse, out Limb hitLimb, Character attacker=null, float damageMultiplier=1)
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
override Vector2? SimPosition
static bool IsOnFriendlyTeam(Character me, Character other)
void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, bool ignoreSeveranceProbabilityModifier=false, Character attacker=null)
static void RemoveByPrefab(CharacterPrefab prefab)
static void UpdateAnimAll(float deltaTime)
static void SaveInventory(Inventory inventory, XElement parentElement)
bool IsCriminal
Do the outpost security officers treat the character as a criminal? Triggers when the character has e...
bool IsFacing(Vector2 targetWorldPos)
A simple check if the character Dir is towards the target or not. Uses the world coordinates.
Dictionary< Identifier, SerializableProperty > SerializableProperties
float GetStatValue(StatTypes statType, bool includeSaved=true)
float GetSkillLevel(Identifier skillIdentifier)
void StackSpeedMultiplier(float val)
void ApplyStatusEffects(ActionType actionType, float deltaTime)
static readonly List< Character > CharacterList
void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent=false)
void DisableLine(Identifier identifier)
bool HasTalent(Identifier identifier)
void DisableLine(string identifier)
void Control(float deltaTime, Camera cam)
void SetAttackTarget(Limb attackLimb, IDamageable damageTarget, Vector2 attackPos)
Vector2 SmoothedCursorPosition
void RecordKill(Character target)
Order GetCurrentOrder(Order order)
override string ToString()
bool IsDualWieldingRangedWeapons()
bool IsFriendly(Character other)
void ResetSpeedMultiplier()
float GetLeftHandPenalty()
Character SelectedCharacter
bool HasRecipeForItem(Identifier recipeIdentifier)
bool HasStoreAccessForItem(ItemPrefab prefab)
virtual void Update(float deltaTime, Camera cam)
Vector2 GetTargetMovement()
bool IsFriendlyNPCTurnedHostile
bool CanBeHealedBy(Character character, bool checkFriendlyTeam=true)
void RemoveAbilityResistance(TalentResistanceIdentifier identifier)
OnAttackedHandler OnAttacked
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
void RemoveAbilityFlag(AbilityFlags abilityFlag)
delegate void OnAttackedHandler(Character attacker, AttackResult attackResult)
void GiveMoney(int amount)
Shows visual notification of money gained by the specific player. Useful for mid-mission monetary gai...
static bool IsTargetVisible(ISpatialEntity target, ISpatialEntity seeingEntity, bool seeThroughWindows=false, bool checkFacing=false)
void ChangeStat(StatTypes statType, float value)
bool IsSameSpeciesOrGroup(Character other)
bool IsHostileEscortee
Set true only, if the character is turned hostile from an escort mission (See EscortMission).
void ClearInput(InputType inputType)
bool TryAddNewTeamChange(string identifier, ActiveTeamChange newTeamChange)
readonly HashSet< LatchOntoAI > Latchers
bool CanBeDraggedBy(Character character)
void TryAdjustHealerSkill(Character healer, float healthChange=0, Affliction affliction=null)
AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, Vector2 impulseDirection, bool playSound=false, Limb targetLimb=null, float penetration=0f)
Apply the specified attack to this character. If the targetLimb is not specified, the limb closest to...
bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel=CharacterInventory.AccessLevel.Limited)
readonly CharacterPrefab Prefab
bool IsOnFriendlyTeam(Character other)
Character SecondLastOrderedCharacter
bool IsOnFriendlyTeam(CharacterTeamType otherTeam)
bool HasItem(Item item, bool requireEquipped=false, InvSlotType? slotType=null)
bool IsOriginallyOnPlayerTeam
void SetMoney(int amount)
float GetDistanceToClosestLimb(Vector2 simPos)
bool HasSelectedAnotherSecondaryItem(Item item)
Item GetEquippedItem(Identifier tagOrIdentifier=default, InvSlotType? slotType=null)
void LoadHeadAttachments()
bool HasEquippedItem(Identifier tagOrIdentifier, bool allowBroken=true, InvSlotType? slotType=null)
void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
const float MaxDragDistance
void StackHealthMultiplier(float val)
Vector2 GetRelativeSimPosition(ISpatialEntity target, Vector2? worldPos=null)
bool DisabledByEvent
MonsterEvents disable monsters (which includes removing them from the character list,...
void TryAdjustAttackerSkill(Character attacker, AttackResult attackResult)
void DoInteractionUpdate(float deltaTime, Vector2 mouseSimPos)
float GetRightHandPenalty()
static Character? Controlled
LocalizedString CustomInteractHUDText
IEnumerable< Attacker > LastAttackers
bool IsKeyDown(InputType inputType)
bool IsAnySelectedItem(Item item)
Is the item either the primary or the secondary selected item?
bool FindItem(ref int itemIndex, out Item targetItem, IEnumerable< Identifier > identifiers=null, bool ignoreBroken=true, IEnumerable< Item > ignoredItems=null, IEnumerable< Identifier > ignoredContainerIdentifiers=null, Func< Item, bool > customPredicate=null, Func< Item, float > customPriorityFunction=null, float maxItemDistance=10000, ISpatialEntity positionalReference=null)
Finds the closest item seeking by identifiers or tags from the world. Ignores items that are outside ...
float GetAbilityResistance(Identifier resistanceId)
float HumanPrefabHealthMultiplier
Health multiplier of the human prefab this character is an instance of (if any)
ActiveTeamChange currentTeamChange
static Character Create(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, bool createNetworkEvent=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Identifier MerchantIdentifier
bool GiveTalent(UInt32 talentIdentifier, bool addingFirstTime=true)
AttackResult AddDamage(Vector2 worldPosition, IEnumerable< Affliction > afflictions, float stun, bool playSound, Vector2? attackImpulse=null, Character attacker=null, float damageMultiplier=1f)
void DespawnNow(bool createNetworkEvents=true)
void CheckTalents(AbilityEffectType abilityEffectType)
static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
Character FocusedCharacter
readonly Dictionary< string, ActiveTeamChange > activeTeamChanges
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)
Identifier GetBaseCharacterSpeciesName()
bool HasJob(Identifier identifier)
bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel=CharacterInventory.AccessLevel.Limited)
Is the inventory accessible to the character? Doesn't check if the character can actually interact wi...
float GetTemporarySpeedReduction()
Speed reduction from the current limb specific damage. Min 0, max 1.
bool IsCommanding
Is the character player or does it have an active ship command manager (an AI controlled sub)?...
bool CanHearCharacter(Character speaker)
void AddAbilityFlag(AbilityFlags abilityFlag)
void GiveJobItems(WayPoint spawnPoint=null)
override Vector2 Position
bool HasAbilityFlag(AbilityFlags abilityFlag)
static void UpdateAll(float deltaTime, Camera cam)
bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime=true)
void SetOrder(Order order, bool isNewOrder, bool speak=true, bool force=false)
Force an order to be set for the character, bypassing hearing checks
Vector2 ApplyMovementLimits(Vector2 targetMovement, float currentSpeed)
void ReleaseSecondaryItem()
void SaveInventory()
Calls SaveInventory(Barotrauma.Inventory, XElement) using 'Inventory' and 'Info.InventoryData'
readonly AnimController AnimController
void ForgiveAttacker(Character character)
float GetDistanceSqrToClosestPlayer()
How far the character is from the closest human player (including spectators)
bool HasUnlockedAllTalents()
void ChangeAbilityResistance(TalentResistanceIdentifier identifier, float value)
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound=true)
List< Hull > GetVisibleHulls()
Returns hulls that are visible to the character, including the current hull. Note that this is not an...
Dictionary< ItemPrefab, double > ItemSelectedDurations
void TeleportTo(Vector2 worldPos)
bool DisableInteract
Prevents the character from interacting with items or characters
IEnumerable< Item >?? HeldItems
Items the character has in their hand slots. Doesn't return nulls and only returns items held in both...
CombatAction CombatAction
static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, bool createNetworkEvent=true, RagdollParams ragdoll=null, bool throwErrorIfNotFound=true, bool spawnInitialItems=true)
Create a new character
float GetDistanceToClosestPlayer()
How far the character is from the closest human player (including spectators)
bool GiveTalent(Identifier talentIdentifier, bool addingFirstTime=true)
LocalizedString TraitorCurrentObjective
void SpawnInventoryItems(Inventory inventory, ContentXElement itemData)
ConversationAction ActiveConversation
float GetAbilityResistance(AfflictionPrefab affliction)
void OnWearablesChanged()
bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity=null, bool seeThroughWindows=false, bool checkFacing=false)
bool IsRemotePlayer
Is the character controlled by another human player (should always be false in single player)
ContentXElement??? FaceAttachment
ContentXElement??? HairWithHatElement
ContentXElement??? MoustacheElement
ContentXElement??? BeardElement
ContentXElement??? HairElement
Stores information about the Character that is needed between rounds in the menu etc....
void ApplySkillGain(Identifier skillIdentifier, float baseGain, bool gainedFromAbility=false, float maxGain=2f)
Increases the characters skill at a rate proportional to their current skill. If you want to increase...
bool IsDisguisedAsAnother
static int HighestManualOrderPriority
Character Character
Note: Can be null.
void CheckDisguiseStatus(bool handleBuff, IdCard idCard=null)
List< Order > CurrentOrders
AccessLevel
How much access other characters have to the inventory? Restricted = Only accessible when character i...
float MaxPerceptionDistance
Contains character data that should be editable in the character editor.
AIParams AI
Parameters for EnemyAIController. Not used by HumanAIController.
readonly CharacterFile File
static bool CompareGroup(Identifier group1, Identifier group2)
Identifier SpeciesTranslationOverride
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
static readonly Identifier HumanSpeciesName
void ActivateTalent(bool addingFirstTime)
void CheckTalent(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
Makes an NPC switch to a combat state (with options for different kinds of behaviors,...
readonly ContentPath Path
IEnumerable< ContentXElement > Elements()
ContentXElement? GetChildElement(string name)
Triggers a "conversation popup" with text and support for different branching options.
void KillCharacter(Character killedCharacter, bool resetCrewListIndex=true)
static EntitySpawner Spawner
virtual Vector2 WorldPosition
const ushort NullEntityID
void AddEntityToRemoveQueue(Entity entity)
static GameSession?? GameSession
static SubEditorScreen SubEditorScreen
static bool IsSingleplayer
static GameScreen GameScreen
static NetworkMember NetworkMember
void ReviveCharacter(Character character)
void KillCharacter(Character character)
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
static bool IsActive(Character c)
static bool IsFriendly(Character me, Character other, bool onlySameTeam=false)
float HealthMultiplierInMultiplayer
static readonly IdRemap DiscardId
void ForceRemoveFromSlot(Item item, int index)
Removes an item from a specific slot. Doesn't do any sanity checks, use with caution!
virtual bool CanBePutInSlot(Item item, int i, bool ignoreCondition=false)
Can the item be put in the specified slot.
virtual bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
List< int > FindIndices(Item item)
Find the indices of all the slots the item is contained in (two-hand items for example can be in mult...
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
void ApplyReceivedState()
void ForceToSlot(Item item, int index)
Forces an item to a specific slot. Doesn't remove the item from existing slots/inventories or do any ...
static bool DraggingItemToWorld
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...
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
override Vector2? SimPosition
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
bool IsShootable
Should the item's Use method be called with the "Use" or with the "Shoot" key?
bool RequireAimToSecondaryUse
If true, the user has to hold the "aim" key before secondary use is registered. True by default.
Inventory ParentInventory
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
void Use(float deltaTime, Character user=null, Limb targetLimb=null, Entity useTarget=null, Character userForOnUsedEvent=null)
Rectangle TransformTrigger(Rectangle trigger, bool world=false)
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
Rectangle InteractionRect
bool TryInteract(Character user, bool ignoreRequiredItems=false, bool forceSelectKey=false, bool forceUseKey=false)
void SecondaryUse(float deltaTime, Character character=null)
static Item Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
override XElement Save(XElement parentElement)
bool IsInsideTrigger(Vector2 worldPosition)
bool RequireAimToUse
If true, the user has to hold the "aim" key before use is registered. False by default.
static readonly List< Item > ItemList
Dictionary< Identifier, SerializableProperty > SerializableProperties
List< ItemComponent > Components
Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id=Entity.NullEntityID, bool callOnItemLoaded=true)
CampaignMode.InteractionType CampaignInteractionType
void CreateStatusEvent(bool loadingRound)
bool DisplaySideBySideWhenLinked
bool InteractThroughWalls
ImmutableArray< Rectangle > Triggers
Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true,...
bool RequireCursorInsideTrigger
bool DisableItemUsageWhenSelected
bool RequireBodyInsideTrigger
readonly ImmutableDictionary< StatTypes, float > HoldableStatValues
The base class for components holding the different functionalities of the item
readonly ItemInventory Inventory
override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
static List< Ladder > List
override bool Select(Character character)
Vector2 TransformedBarrelPos
void GiveJobItems(Character character, WayPoint spawnPoint=null)
LatchOntoAI(XElement element, EnemyAIController enemyAI)
float GetRealWorldDepth(float worldPositionY)
Calculate the "real" depth in meters from the surface of Europa (the value you see on the nav termina...
AttackResult AddDamage(Vector2 simPosition, float damage, float bleedingDamage, float burnDamage, bool playSound)
readonly Character character
void ApplyStatusEffects(ActionType actionType, float deltaTime)
readonly LimbParams Params
bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance=-1, Limb targetLimb=null)
Returns true if the attack successfully hit something. If the distance is not given,...
Color InitialLightSourceColor
readonly List< WearableSprite > OtherWearables
readonly JointParams Params
readonly RevoluteJoint revoluteJoint
object Call(string name, params object[] args)
HashSet< Character > UpdatePriorityCharacters
Mersenne Twister based random
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
readonly List< MapEntity > linkedTo
string ApplyDistanceEffect(Character listener)
static Color[] MessageColor
static bool CanUseRadio(Character sender, bool ignoreJamming=false)
CharacterInfo CharacterInfo
readonly Entity TargetEntity
readonly Character OrderGiver
readonly Identifier Option
readonly int ManualPriority
static readonly PrefabCollection< OrderPrefab > Prefabs
void Play(Character player)
void ApplyLinearImpulse(Vector2 impulse)
readonly ContentFile ContentFile
ContentPackage? ContentPackage
readonly Identifier Identifier
void FindHull(Vector2? worldPosition=null, bool setSubmarine=true)
void ResetPullJoints(Func< Limb, bool > condition=null)
bool IsHoldingToRope
Is attached to something with a rope.
bool SeverLimbJoint(LimbJoint limbJoint)
bool IsHangingWithRope
Is hanging to something with a rope, so that can reel towards it. Currently only possible in water.
void SetPosition(Vector2 simPosition, bool lerp=false, bool ignorePlatforms=true, bool forceMainLimbToCollider=false, bool moveLatchers=true)
bool? SimplePhysicsEnabled
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.
Vector2 GetCenterOfMass()
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
float SkillIncreasePerHostileDamage
float SkillIncreasePerFriendlyHealed
static SkillSettings Current
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
bool HasTargetType(TargetType targetType)
static StatusEffect Load(ContentXElement element, string parentDebugName)
void AddNearbyTargets(Vector2 worldPosition, List< ISerializableEntity > targets)
readonly bool OnlyWhenDamagedByPlayer
If enabled, the effect only executes when the entity receives damage from a player character (a chara...
bool HasRequiredAfflictions(AttackResult attackResult)
readonly LimbType[] targetLimbs
Which types of limbs this effect can target? Only valid when targeting characters or limbs.
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
override Vector2? WorldPosition
static Vector2 GetRelativeSimPosition(ISpatialEntity from, ISpatialEntity to, Vector2? targetWorldPos=null)
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).
override Vector2 SimPosition
static Fixture LastPickedFixture
override Vector2? Position
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
static Vector2 LastPickedPosition
static readonly PrefabCollection< TalentPrefab > TalentPrefabs
Interface for entities that the clients can send events to the server
Interface for entities that handle ServerNetObject.ENTITY_POSITION
AbilityFlags
AbilityFlags are a set of toggleable flags that can be applied to characters.
ActionType
ActionTypes define when a StatusEffect is executed.
@ InWater
Executes continuously when the entity is submerged. Valid for items and characters.
@ OnDeath
Executes when the character dies. Only valid for characters.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier)