Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2023-12-14 11:56:39 -03:00
376 changed files with 7775 additions and 2879 deletions
@@ -70,10 +70,18 @@ namespace Barotrauma
}
foreach (var item in HeldItems)
{
if (item.body != null)
if (item.body == null) { continue; }
if (!enabled)
{
item.body.Enabled = enabled;
item.body.Enabled = false;
}
else if (item.GetComponent<Holdable>() is { IsActive: true })
{
//held items includes all items in hand slots
//we only want to enable the physics body if it's an actual holdable item, not e.g. a wearable item like handcuffs
item.body.Enabled = true;
}
}
AnimController.Collider.Enabled = value;
}
@@ -939,10 +947,16 @@ namespace Barotrauma
{
var prevSelectedItem = _selectedItem;
_selectedItem = value;
if (value is not null)
{
CheckTalents(AbilityEffectType.OnItemSelected, new AbilityItemSelected(value));
}
#if CLIENT
HintManager.OnSetSelectedItem(this, prevSelectedItem, _selectedItem);
if (Controlled == this)
{
_selectedItem?.GetComponent<Fabricator>()?.RefreshSelectedItem();
if (_selectedItem == null)
{
GameMain.GameSession?.CrewManager?.ResetCrewList();
@@ -1101,6 +1115,15 @@ namespace Barotrauma
set { CharacterHealth.Unkillable = value; }
}
/// <summary>
/// Is the health interface available on this character? Can be used by status effects
/// </summary>
public bool UseHealthWindow
{
get { return CharacterHealth.UseHealthWindow; }
set { CharacterHealth.UseHealthWindow = value; }
}
public CampaignMode.InteractionType CampaignInteractionType;
public Identifier MerchantIdentifier;
@@ -1284,7 +1307,8 @@ namespace Barotrauma
{
if (!VariantOf.IsEmpty)
{
DebugConsole.ThrowError("The variant system does not yet support humans, sorry. It does support other humanoids though!");
DebugConsole.ThrowError("The variant system does not yet support humans, sorry. It does support other humanoids though!",
contentPackage: Prefab.ContentPackage);
}
if (characterInfo == null)
{
@@ -1408,7 +1432,8 @@ namespace Barotrauma
if (matchingAffliction == null || nonHuskedSpeciesName.IsEmpty)
{
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"
+ "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\"");
+ "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\"",
contentPackage: Prefab.ContentPackage);
// Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg.
nonHuskedSpeciesName = IsHumanoid ? CharacterPrefab.HumanSpeciesName : "crawler".ToIdentifier();
speciesName = nonHuskedSpeciesName;
@@ -1690,31 +1715,21 @@ namespace Barotrauma
GameMain.LuaCs.Hook.Call("character.giveJobItems", this, spawnPoint);
}
public void GiveIdCardTags(WayPoint spawnPoint, bool requireSpawnPointTagsNotGiven = true, bool createNetworkEvent = false)
public void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent = false)
{
GiveIdCardTags(spawnPoint.ToEnumerable(), requireSpawnPointTagsNotGiven, createNetworkEvent);
}
public void GiveIdCardTags(IEnumerable<WayPoint> spawnPoints, bool requireSpawnPointTagsNotGiven = true, bool createNetworkEvent = false)
{
if (info?.Job == null || spawnPoints == null) { return; }
if (info?.Job == null || spawnPoint == null) { return; }
foreach (Item item in Inventory.AllItems)
{
if (item?.GetComponent<IdCard>() is not IdCard idCard) { continue; }
if (requireSpawnPointTagsNotGiven)
var idCard = item?.GetComponent<IdCard>();
if (idCard == null) { continue; }
//if the card belongs to someone else, don't add any tags.
//otherwise you can gain access to places you shouldn't by temporarily giving the card to someone (e.g. a captain bot) at the end of the round
if (idCard.OwnerName != info.Name) { continue; }
foreach (string s in spawnPoint.IdCardTags)
{
if (idCard.SpawnPointTagsGiven) { continue; }
item.AddTag(s);
}
foreach (var spawnPoint in spawnPoints)
{
foreach (string s in spawnPoint.IdCardTags)
{
item.AddTag(s);
}
}
idCard.SpawnPointTagsGiven = true;
if (createNetworkEvent && GameMain.NetworkMember is { IsServer: true })
{
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ChangePropertyEventData(item.SerializableProperties[nameof(item.Tags).ToIdentifier()], item));
@@ -2299,40 +2314,40 @@ namespace Barotrauma
return AnimController.GetLimb(LimbType.Head) ?? AnimController.GetLimb(LimbType.Torso) ?? AnimController.MainLimb;
}
public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null, bool checkFacing = false)
public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null, bool seeThroughWindows = false, bool checkFacing = false)
{
seeingEntity ??= AnimController.SimplePhysicsEnabled ? this : GetSeeingLimb();
if (target is Character targetCharacter)
{
return IsCharacterVisible(targetCharacter, seeingEntity, checkFacing);
return IsCharacterVisible(targetCharacter, seeingEntity, seeThroughWindows, checkFacing);
}
else
{
return CheckVisibility(target, seeingEntity, checkFacing);
return CheckVisibility(target, seeingEntity, seeThroughWindows, checkFacing);
}
}
public static bool IsTargetVisible(ISpatialEntity target, ISpatialEntity seeingEntity, bool checkFacing = false)
public static bool IsTargetVisible(ISpatialEntity target, ISpatialEntity seeingEntity, bool seeThroughWindows = false, bool checkFacing = false)
{
if (seeingEntity is Character seeingCharacter)
{
return seeingCharacter.CanSeeTarget(target, checkFacing: checkFacing);
return seeingCharacter.CanSeeTarget(target, seeThroughWindows: seeThroughWindows, checkFacing: checkFacing);
}
if (target is Character targetCharacter)
{
return IsCharacterVisible(targetCharacter, seeingEntity, checkFacing);
return IsCharacterVisible(targetCharacter, seeingEntity, seeThroughWindows, checkFacing);
}
else
{
return CheckVisibility(target, seeingEntity, checkFacing);
return CheckVisibility(target, seeingEntity, seeThroughWindows, checkFacing);
}
}
private static bool IsCharacterVisible(Character target, ISpatialEntity seeingEntity, bool checkFacing = false)
private static bool IsCharacterVisible(Character target, ISpatialEntity seeingEntity, bool seeThroughWindows = false, bool checkFacing = false)
{
System.Diagnostics.Debug.Assert(target != null);
if (target == null || target.Removed) { return false; }
if (CheckVisibility(target, seeingEntity, checkFacing)) { return true; }
if (CheckVisibility(target, seeingEntity, seeThroughWindows, checkFacing)) { return true; }
if (!target.AnimController.SimplePhysicsEnabled)
{
//find the limbs that are furthest from the target's position (from the viewer's point of view)
@@ -2361,13 +2376,13 @@ namespace Barotrauma
continue;
}
}
if (leftExtremity != null && CheckVisibility(leftExtremity, seeingEntity, checkFacing)) { return true; }
if (rightExtremity != null && CheckVisibility(rightExtremity, seeingEntity, checkFacing)) { return true; }
if (leftExtremity != null && CheckVisibility(leftExtremity, seeingEntity, seeThroughWindows, checkFacing)) { return true; }
if (rightExtremity != null && CheckVisibility(rightExtremity, seeingEntity, seeThroughWindows, checkFacing)) { return true; }
}
return false;
}
private static bool CheckVisibility(ISpatialEntity target, ISpatialEntity seeingEntity, bool checkFacing = false)
private static bool CheckVisibility(ISpatialEntity target, ISpatialEntity seeingEntity, bool seeThroughWindows = true, bool checkFacing = false)
{
System.Diagnostics.Debug.Assert(target != null);
if (target == null) { return false; }
@@ -2378,38 +2393,41 @@ namespace Barotrauma
{
if (Math.Sign(diff.X) != seeingCharacter.AnimController.Dir) { return false; }
}
Body closestBody;
//both inside the same sub (or both outside)
//OR the we're inside, the other character outside
if (target.Submarine == seeingEntity.Submarine || target.Submarine == null)
{
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
return Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff, blocksVisibilityPredicate: IsBlocking) == null;
}
//we're outside, the other character inside
else if (seeingEntity.Submarine == null)
{
closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff);
return Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff, blocksVisibilityPredicate: IsBlocking) == null;
}
//both inside different subs
else
{
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
if (!IsBlocking(closestBody))
{
closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff);
}
return
Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff, blocksVisibilityPredicate: IsBlocking) == null &&
Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff, blocksVisibilityPredicate: IsBlocking) == null;
}
return !IsBlocking(closestBody);
bool IsBlocking(Body body)
bool IsBlocking(Fixture f)
{
var body = f.Body;
if (body == null) { return false; }
if (body.UserData is Structure wall && wall.CastShadow)
if (body.UserData is Structure wall)
{
if (!wall.CastShadow && seeThroughWindows) { return false; }
return wall != target;
}
else if (body.UserData is Item item)
{
if (item.GetComponent<Door>() is { HasWindow: true } door && seeThroughWindows)
{
if (door.IsPositionOnWindow(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition))) { return false; }
}
return item != target;
}
return true;
@@ -2504,9 +2522,21 @@ namespace Barotrauma
if (inventory.Owner is Item item)
{
if (!CanInteractWith(item) && !item.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; }
ItemContainer container = item.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
if (container != null && !container.HasRequiredItems(this, addMessage: false)) { return false; }
if (!CanInteractWith(item))
{
//could be simplified with LINQ, but that'd require capturing variables which we shouldn't do in a method that's called as frequently as this
foreach (var linkedEntity in item.linkedTo)
{
if (linkedEntity is Item linkedItem && linkedItem.DisplaySideBySideWhenLinked && CanInteractWith(linkedItem)) { return true; }
}
return false;
}
ItemContainer container = (inventory as ItemInventory)?.Container;
if (container != null)
{
if (!container.HasRequiredItems(this, addMessage: false)) { return false; }
if (!container.DrawInventory) { return false; }
}
}
return true;
}
@@ -2771,9 +2801,17 @@ namespace Barotrauma
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
{
var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item && (body.UserData as ItemComponent)?.Item != item && Submarine.LastPickedFixture?.UserData as Item != item)
{
return false;
if (body != null)
{
var otherItem = body.UserData as Item ?? (body.UserData as ItemComponent)?.Item;
if (otherItem != item &&
(body.UserData as ItemComponent)?.Item != item &&
/*allow interacting through open doors (e.g. duct blocks' colliders stay active despite being open)*/
otherItem?.GetComponent<Door>() is not { IsOpen: true } &&
Submarine.LastPickedFixture?.UserData as Item != item)
{
return false;
}
}
}
@@ -2800,7 +2838,12 @@ namespace Barotrauma
public void DeselectCharacter()
{
if (SelectedCharacter == null) { return; }
SelectedCharacter.AnimController?.ResetPullJoints();
if (!SelectedCharacter.AllowInput)
{
//we cannot reset the pull joints if the target is conscious (moving on its own),
//that'd interfere with its animations
SelectedCharacter.AnimController?.ResetPullJoints();
}
SelectedCharacter = null;
}
@@ -3316,10 +3359,7 @@ namespace Barotrauma
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.2f; }
}
if (IsRagdolled)
{
SetInput(InputType.Ragdoll, false, true);
}
SetInput(InputType.Ragdoll, false, IsRagdolled);
}
if (!wasRagdolled && IsRagdolled)
{
@@ -3577,6 +3617,8 @@ namespace Barotrauma
private void Despawn(bool createNetworkEvents = true)
{
if (!EnableDespawn) { return; }
Identifier despawnContainerId =
IsHuman ?
"despawncontainer".ToIdentifier() :
@@ -3658,10 +3700,12 @@ namespace Barotrauma
float massFactor = (float)Math.Sqrt(Mass / 20);
float targetRange = Math.Min(minRange + massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Visibility, maxAIRange);
float newRange = MathHelper.SmoothStep(aiTarget.SightRange, targetRange, deltaTime * aiTargetChangeSpeed);
newRange *= 1.0f + GetStatValue(StatTypes.SightRangeMultiplier);
if (!float.IsNaN(newRange))
{
aiTarget.SightRange = newRange;
}
}
private void UpdateSoundRange(float deltaTime)
@@ -3676,6 +3720,7 @@ namespace Barotrauma
float massFactor = (float)Math.Sqrt(Mass / 10);
float targetRange = Math.Min(massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Noise, maxAIRange);
float newRange = MathHelper.SmoothStep(aiTarget.SoundRange, targetRange, deltaTime * aiTargetChangeSpeed);
newRange *= 1.0f + GetStatValue(StatTypes.SoundRangeMultiplier);
if (!float.IsNaN(newRange))
{
aiTarget.SoundRange = newRange;
@@ -3995,15 +4040,15 @@ namespace Barotrauma
CharacterHealth.SetAllDamage(damageAmount, bleedingDamageAmount, burnDamageAmount);
}
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true)
{
return ApplyAttack(attacker, worldPosition, attack, deltaTime, playSound, null);
return ApplyAttack(attacker, worldPosition, attack, deltaTime, impulseDirection, playSound);
}
/// <summary>
/// Apply the specified attack to this character. If the targetLimb is not specified, the limb closest to worldPosition will receive the damage.
/// </summary>
public AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false, Limb targetLimb = null, float penetration = 0f)
public AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, Vector2 impulseDirection, bool playSound = false, Limb targetLimb = null, float penetration = 0f)
{
if (Removed)
{
@@ -4015,7 +4060,16 @@ namespace Barotrauma
Limb limbHit = targetLimb;
float attackImpulse = attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier * deltaTime;
float impulseMagnitude = (attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier) * deltaTime;
Vector2 attackImpulse = Vector2.Zero;
if (Math.Abs(impulseMagnitude) > 0.0f)
{
impulseDirection = impulseDirection.LengthSquared() > 0.0001f ?
Vector2.Normalize(impulseDirection) :
Vector2.UnitX;
attackImpulse = impulseDirection * impulseMagnitude;
}
AbilityAttackData attackData = new AbilityAttackData(attack, this, attacker);
IEnumerable<Affliction> attackAfflictions;
@@ -4144,12 +4198,12 @@ namespace Barotrauma
}
}
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null, float damageMultiplier = 1f)
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, Vector2? attackImpulse = null, Character attacker = null, float damageMultiplier = 1f)
{
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker, damageMultiplier: damageMultiplier);
return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse ?? Vector2.Zero, out _, attacker, damageMultiplier: damageMultiplier);
}
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1)
public AttackResult AddDamage(Vector2 worldPosition, IEnumerable<Affliction> afflictions, float stun, bool playSound, Vector2 attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1)
{
hitLimb = null;
@@ -4182,7 +4236,7 @@ namespace Barotrauma
CreatureMetrics.RecordKill(target.SpeciesName);
}
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false)
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)
{
if (Removed) { return new AttackResult(); }
@@ -4215,18 +4269,17 @@ namespace Barotrauma
}
Vector2 dir = hitLimb.WorldPosition - worldPosition;
if (Math.Abs(attackImpulse) > 0.0f)
if (attackImpulse.LengthSquared() > 0.0f)
{
Vector2 diff = dir;
if (diff == Vector2.Zero) { diff = Rand.Vector(1.0f); }
Vector2 impulse = Vector2.Normalize(diff) * attackImpulse;
Vector2 hitPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff);
hitLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
hitLimb.body.ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
var mainLimb = hitLimb.character.AnimController.MainLimb;
if (hitLimb != mainLimb)
{
// Always add force to mainlimb
mainLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
mainLimb.body.ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
bool wasDead = IsDead;
@@ -4325,19 +4378,16 @@ namespace Barotrauma
}
if (medicalDamage > 0)
{
IncreaseSkillLevel("medical".ToIdentifier(), medicalDamage);
IncreaseSkillLevel(Tags.MedicalSkill, medicalDamage);
}
if (weaponDamage > 0)
{
IncreaseSkillLevel("weapons".ToIdentifier(), weaponDamage);
IncreaseSkillLevel(Tags.WeaponsSkill, weaponDamage);
}
void IncreaseSkillLevel(Identifier skill, float damage)
{
float attackerSkillLevel = attacker.GetSkillLevel(skill);
// The formula is too generous on low skill levels, hence the minimum divider.
float minSkillDivider = 15f;
attacker.Info?.IncreaseSkillLevel(skill, damage * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, minSkillDivider));
attacker.Info?.ApplySkillGain(skill, damage * SkillSettings.Current.SkillIncreasePerHostileDamage, false, 1f);
}
}
@@ -4351,12 +4401,10 @@ namespace Barotrauma
{
medicalGain += affliction.Strength * affliction.Prefab.MedicalSkillGain;
}
if (medicalGain <= 0) { return; }
Identifier skill = new Identifier("medical");
float attackerSkillLevel = healer.GetSkillLevel(skill);
// The formula is too generous on low skill levels, hence the minimum divider.
float minSkillDivider = 15f;
healer.Info?.IncreaseSkillLevel(skill, medicalGain * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, minSkillDivider));
if (medicalGain > 0)
{
healer.Info?.ApplySkillGain(Tags.MedicalItem, medicalGain * SkillSettings.Current.SkillIncreasePerFriendlyHealed);
}
}
/// <summary>
@@ -4991,8 +5039,10 @@ namespace Barotrauma
private readonly List<Hull> visibleHulls = new List<Hull>();
private readonly HashSet<Hull> tempList = new HashSet<Hull>();
/// <summary>
/// Returns hulls that are visible to the player, including the current hull.
/// Returns hulls that are visible to the character, including the current hull.
/// Note that this is not an accurate visibility check, it only checks for open gaps between the adjacent and linked hulls.
/// Can be heavy if used every frame.
/// </summary>
public List<Hull> GetVisibleHulls()
@@ -5006,7 +5056,9 @@ namespace Barotrauma
float maxDistance = 1000f;
foreach (var hull in adjacentHulls)
{
if (hull.ConnectedGaps.Any(g => g.Open > 0.9f && g.linkedTo.Contains(CurrentHull) &&
if (hull.ConnectedGaps.Any(g =>
g.Open > 0.9f &&
g.linkedTo.Contains(CurrentHull) &&
Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2)))
{
if (Vector2.DistanceSquared(hull.WorldPosition, WorldPosition) < Math.Pow(maxDistance, 2))
@@ -5047,7 +5099,7 @@ namespace Barotrauma
public bool IsEngineer => HasJob("engineer");
public bool IsMechanic => HasJob("mechanic");
public bool IsMedic => HasJob("medicaldoctor");
public bool IsSecurity => HasJob("securityofficer") || HasJob("vipsecurityofficer");
public bool IsSecurity => HasJob("securityofficer") || HasJob("vipsecurityofficer") || HasJob("outpostsecurityofficer");
public bool IsAssistant => HasJob("assistant");
public bool IsWatchman => HasJob("watchman");
public bool IsVip => HasJob("prisoner");
@@ -5566,4 +5618,12 @@ namespace Barotrauma
public Character Character { get; set; }
}
class AbilityItemSelected : AbilityObject, IAbilityItem
{
public AbilityItemSelected(Item item)
{
Item = item;
}
public Item Item { get; set; }
}
}