Build 0.20.0.0

This commit is contained in:
Markus Isberg
2022-10-27 17:54:57 +03:00
parent 05c7b1f869
commit edaf4b09fe
197 changed files with 4344 additions and 1773 deletions
@@ -442,6 +442,7 @@ namespace Barotrauma
base.Update(deltaTime);
UpdateTriggers(deltaTime);
Character.ClearInputs();
Reverse = false;
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
if (steeringManager == insideSteering)
@@ -804,10 +805,6 @@ namespace Barotrauma
Reverse = true;
run = true;
}
else
{
Reverse = false;
}
SteeringManager.SteeringManual(deltaTime, dir * 0.2f);
}
else
@@ -1490,40 +1487,26 @@ namespace Barotrauma
canAttack = angle < MathHelper.ToRadians(AttackLimb.attack.RequiredAngle);
if (canAttack && AttackLimb.attack.AvoidFriendlyFire)
{
float minDistance = MathUtils.Pow(ConvertUnits.ToDisplayUnits(Character.AnimController.Collider.GetMaxExtent() * 3), 2);
bool IsFarEnough(Character other) => Vector2.DistanceSquared(Character.WorldPosition, other.WorldPosition) > minDistance;
if (SwarmBehavior != null)
canAttack = !IsBlocked(Character.GetRelativeSimPosition(SelectedAiTarget.Entity));
bool IsBlocked(Vector2 targetPosition)
{
canAttack = SwarmBehavior.Members.All(c => c == Character || IsFarEnough(c));
}
else
{
canAttack = Character.CharacterList.All(c => c == Character || !Character.IsFriendly(c) || IsFarEnough(c));
}
if (canAttack)
{
canAttack = !IsBlocked(attackSimPos) && !IsBlocked(AttackLimb.SimPosition + forward * ConvertUnits.ToSimUnits(AttackLimb.attack.Range));
bool IsBlocked(Vector2 targetPosition)
foreach (var body in Submarine.PickBodies(AttackLimb.SimPosition, targetPosition, myBodies, Physics.CollisionCharacter))
{
foreach (var body in Submarine.PickBodies(AttackLimb.SimPosition, targetPosition, myBodies, Physics.CollisionCharacter))
Character hitTarget = null;
if (body.UserData is Character c)
{
Character hitTarget = null;
if (body.UserData is Character c)
{
hitTarget = c;
}
else if (body.UserData is Limb limb)
{
hitTarget = limb.character;
}
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
{
return true;
}
hitTarget = c;
}
else if (body.UserData is Limb limb)
{
hitTarget = limb.character;
}
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
{
return true;
}
return false;
}
return false;
}
}
}
@@ -1854,7 +1837,33 @@ namespace Barotrauma
}
}
if (!canAttack || distance > Math.Min(AttackLimb.attack.Range * 0.9f, 100))
if (AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
{
bool advance = !canAttack && Character.InWater || distance > attackLimb.attack.Range * 0.9f;
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
if (fallBack)
{
Reverse = true;
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
}
else if (advance)
{
if (pathSteering != null)
{
pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize);
}
else
{
SteeringManager.SteeringSeek(steerPos, 10);
}
}
else if (!Character.InWater)
{
SteeringManager.Reset();
FaceTarget(SelectedAiTarget.Entity);
}
}
else if (!canAttack || distance > Math.Min(AttackLimb.attack.Range * 0.9f, 100))
{
if (pathSteering != null)
{
@@ -1865,20 +1874,30 @@ namespace Barotrauma
SteeringManager.SteeringSeek(steerPos, 10);
}
}
else if (AttackLimb.attack.Ranged)
{
// Too close
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
}
if (Character.CurrentHull == null && (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)))
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30);
}
}
}
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity as IDamageable;
if (AttackLimb?.attack is Attack { Ranged: true} attack)
{
Limb limb = GetLimbToRotate(attack);
if (limb != null)
{
Vector2 toTarget = damageTarget.WorldPosition - limb.WorldPosition;
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
limb.body.SuppressSmoothRotationCalls = false;
float angle = MathUtils.VectorToAngle(toTarget);
limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
limb.body.SuppressSmoothRotationCalls = true;
}
}
if (canAttack)
{
if (!UpdateLimbAttack(deltaTime, AttackLimb, attackSimPos, distance, attackTargetLimb))
if (!UpdateLimbAttack(deltaTime, attackSimPos, damageTarget, distance, attackTargetLimb))
{
IgnoreTarget(SelectedAiTarget);
}
@@ -2114,13 +2133,14 @@ namespace Barotrauma
}
// 10 dmg, 100 health -> 0.1
private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f);
private static float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f);
private bool UpdateLimbAttack(float deltaTime, Limb attackingLimb, Vector2 attackSimPos, float distance = -1, Limb targetLimb = null)
private bool UpdateLimbAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, float distance = -1, Limb targetLimb = null)
{
if (SelectedAiTarget?.Entity == null) { return false; }
if (attackingLimb?.attack == null) { return false; }
ActiveAttack = attackingLimb.attack;
if (AttackLimb?.attack == null) { return false; }
if (damageTarget == null) { return false; }
ActiveAttack = AttackLimb.attack;
if (wallTarget != null)
{
// If the selected target is not the wall target, make the wall target the selected target.
@@ -2131,83 +2151,94 @@ namespace Barotrauma
State = AIState.Attack;
}
}
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
if (damageTarget != null)
if (damageTarget == null) { return false; }
if (ActiveAttack.Ranged && ActiveAttack.RequiredAngleToShoot > 0)
{
if (Character.Params.CanInteract && Character.Inventory != null)
Limb referenceLimb = GetLimbToRotate(ActiveAttack);
if (referenceLimb != null)
{
// Use equipped items (weapons)
Item item = GetEquippedItem(attackingLimb);
if (item != null)
Vector2 toTarget = damageTarget.WorldPosition - referenceLimb.WorldPosition;
float offset = referenceLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
Vector2 forward = VectorExtensions.Forward(referenceLimb.body.TransformedRotation - offset * referenceLimb.Dir);
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(forward, toTarget));
if (angle > ActiveAttack.RequiredAngleToShoot)
{
if (item.RequireAimToUse)
{
if (!Aim(deltaTime, damageTarget as ISpatialEntity, item))
{
// Valid target, but can't shoot -> return true so that it will not be ignored.
return true;
}
}
Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
item.Use(deltaTime, Character);
return true;
}
}
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
if (!ActiveAttack.IsRunning)
}
if (Character.Params.CanInteract && Character.Inventory != null)
{
// Use equipped items (weapons)
Item item = GetEquippedItem(AttackLimb);
if (item != null)
{
if (item.RequireAimToUse)
{
if (!Aim(deltaTime, damageTarget as ISpatialEntity, item))
{
// Valid target, but can't shoot -> return true so that it will not be ignored.
return true;
}
}
Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
item.Use(deltaTime, Character);
}
}
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
if (!ActiveAttack.IsRunning)
{
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
attackingLimb,
AttackLimb,
damageTarget,
targetLimb,
SimPosition));
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif
}
}
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
if (AttackLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
if (ActiveAttack.CoolDownTimer > 0)
{
if (attackingLimb.attack.CoolDownTimer > 0)
SetAimTimer(Math.Min(ActiveAttack.CoolDown, 1.5f));
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
float greed = AIParams.AggressionGreed;
if (damageTarget is not Barotrauma.Character)
{
// Halve the greed for attacking non-characters.
greed /= 2;
}
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
}
if (LatchOntoAI != null && SelectedAiTarget.Entity is Character targetCharacter)
{
LatchOntoAI.SetAttachTarget(targetCharacter);
}
if (!ActiveAttack.Ranged)
{
if (damageTarget.Health > 0 && attackResult.Damage > 0)
{
SetAimTimer(Math.Min(attackingLimb.attack.CoolDown, 1.5f));
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
float greed = AIParams.AggressionGreed;
if (!(damageTarget is Character))
if (damageTarget is not Barotrauma.Character)
{
// Halve the greed for attacking non-characters.
greed /= 2;
}
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
}
if (LatchOntoAI != null && SelectedAiTarget.Entity is Character targetCharacter)
else
{
LatchOntoAI.SetAttachTarget(targetCharacter);
}
if (!attackingLimb.attack.Ranged)
{
if (damageTarget.Health > 0 && attackResult.Damage > 0)
{
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
float greed = AIParams.AggressionGreed;
if (!(damageTarget is Character))
{
// Halve the greed for attacking non-characters.
greed /= 2;
}
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
}
else
{
selectedTargetMemory.Priority -= Math.Max(selectedTargetMemory.Priority / 2, 1);
return selectedTargetMemory.Priority > 1;
}
selectedTargetMemory.Priority -= Math.Max(selectedTargetMemory.Priority / 2, 1);
return selectedTargetMemory.Priority > 1;
}
}
return true;
}
return false;
return true;
}
private float aimTimer;
@@ -2299,7 +2330,6 @@ namespace Barotrauma
{
if (attackVector == null)
{
// TODO: test adding some random variance here?
attackVector = attackWorldPos - WorldPosition;
}
Vector2 dir = Vector2.Normalize(followThrough ? attackVector.Value : -attackVector.Value);
@@ -2319,6 +2349,16 @@ namespace Barotrauma
return true;
}
private Limb GetLimbToRotate(Attack attack)
{
Limb limb = AttackLimb;
if (attack.RotationLimbIndex > -1 && attack.RotationLimbIndex < Character.AnimController.Limbs.Length)
{
limb = Character.AnimController.Limbs[attack.RotationLimbIndex];
}
return limb;
}
#endregion
#region Eat
@@ -3429,7 +3469,7 @@ namespace Barotrauma
private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
=> ChangeParams(tag.ToIdentifier(), state, priority, onlyExisting);
private void ChangeParams(Identifier tag, AIState state, float? priority = null, bool onlyExisting = false)
private void ChangeParams(Identifier tag, AIState state, float? priority = null, bool onlyExisting = false, bool ignoreAttacksIfNotInSameSub = false)
{
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
{
@@ -3437,6 +3477,11 @@ namespace Barotrauma
{
if (AIParams.TryAddNewTarget(tag, state, priority ?? minPriority, out targetParams))
{
if (state == AIState.Attack)
{
// Only applies to new temp target params. Shouldn't affect any existing definitions (handled below).
targetParams.IgnoreIfNotInSameSub = ignoreAttacksIfNotInSameSub;
}
tempParams.Add(tag, targetParams);
}
}
@@ -3470,7 +3515,7 @@ namespace Barotrauma
{
isStateChanged = true;
SetStateResetTimer();
ChangeParams(target.SpeciesName, state, priority);
ChangeParams(target.SpeciesName, state, priority, ignoreAttacksIfNotInSameSub: !target.IsHuman);
if (target.IsHuman)
{
priority = GetTargetParams("human")?.Priority;
@@ -748,6 +748,9 @@ namespace Barotrauma
}
if (!character.HasEquippedItem(Weapon, predicate: IsHandSlotType))
{
//clear aim and shoot inputs so the bot doesn't immediately fire the weapon if it was previously e.g. using a scooter
character.ClearInput(InputType.Aim);
character.ClearInput(InputType.Shoot);
Weapon.TryInteract(character, forceSelectKey: true);
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
if (character.Inventory.TryPutItem(Weapon, character, slots))
@@ -764,7 +767,7 @@ namespace Barotrauma
}
return true;
bool IsHandSlotType(InvSlotType s) => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand);
static bool IsHandSlotType(InvSlotType s) => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand);
}
private float findHullTimer;
@@ -186,8 +186,8 @@ namespace Barotrauma
{
if (character.SelectedItem != Item)
{
if (Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) ||
Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true))
if (Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true) ||
Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true))
{
character.SelectedItem = Item;
}
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using static Barotrauma.CharacterParams;
namespace Barotrauma
{
@@ -44,7 +45,7 @@ namespace Barotrauma
public float PlayTimer { get; set; }
private float? unstunY { get; set; }
public EnemyAIController AiController { get; private set; } = null;
public EnemyAIController AIController { get; private set; } = null;
public Character Owner { get; set; }
@@ -134,8 +135,8 @@ namespace Barotrauma
aggregate += Items[i].Commonness;
if (aggregate >= r && Items[i].Prefab != null)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetProducedItem:" + pet.AiController.Character.SpeciesName + ":" + Items[i].Prefab.Identifier);
Entity.Spawner.AddItemToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition);
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetProducedItem:" + pet.AIController.Character.SpeciesName + ":" + Items[i].Prefab.Identifier);
Entity.Spawner.AddItemToSpawnQueue(Items[i].Prefab, pet.AIController.Character.WorldPosition);
break;
}
}
@@ -160,8 +161,8 @@ namespace Barotrauma
public PetBehavior(XElement element, EnemyAIController aiController)
{
AiController = aiController;
AiController.Character.CanBeDragged = true;
AIController = aiController;
AIController.Character.CanBeDragged = true;
MaxHappiness = element.GetAttributeFloat("maxhappiness", 100.0f);
MaxHunger = element.GetAttributeFloat("maxhunger", 100.0f);
@@ -218,7 +219,7 @@ namespace Barotrauma
bool success = OnEat(item.GetTags());
if (success)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetEat:" + AiController.Character.SpeciesName + ":" + item.Prefab.Identifier);
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetEat:" + AIController.Character.SpeciesName + ":" + item.Prefab.Identifier);
}
return success;
}
@@ -229,7 +230,7 @@ namespace Barotrauma
bool success = OnEat("dead".ToIdentifier());
if (success)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetEat:" + AiController.Character.SpeciesName + ":" + character.SpeciesName);
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetEat:" + AIController.Character.SpeciesName + ":" + character.SpeciesName);
}
return success;
}
@@ -252,7 +253,7 @@ namespace Barotrauma
Hunger += foods[i].Hunger;
Happiness += foods[i].Happiness;
#if CLIENT
AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.5f);
AIController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.5f);
#endif
return true;
}
@@ -265,20 +266,20 @@ namespace Barotrauma
if (PlayTimer > 0.0f) { return; }
if (Owner == null) { Owner = player; }
PlayTimer = 5.0f;
AiController.Character.IsRagdolled = true;
AIController.Character.IsRagdolled = true;
Happiness += 10.0f;
AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce);
unstunY = AiController.Character.SimPosition.Y;
AIController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce);
unstunY = AIController.Character.SimPosition.Y;
#if CLIENT
AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.9f);
AIController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.9f);
#endif
}
public string GetTagName()
{
if (AiController.Character.Inventory != null)
if (AIController.Character.Inventory != null)
{
foreach (Item item in AiController.Character.Inventory.AllItems)
foreach (Item item in AIController.Character.Inventory.AllItems)
{
var tag = item.GetComponent<NameTag>();
if (tag != null && !string.IsNullOrWhiteSpace(tag.WrittenName))
@@ -293,7 +294,7 @@ namespace Barotrauma
public void Update(float deltaTime)
{
var character = AiController.Character;
var character = AIController.Character;
if (character?.Removed ?? true || character.IsDead) { return; }
if (unstunY.HasValue)
@@ -332,16 +333,27 @@ namespace Barotrauma
Food food = foods[i];
if (Hunger >= food.HungerRange.X && Hunger <= food.HungerRange.Y)
{
if (food.TargetParams == null &&
AiController.AIParams.TryAddNewTarget(food.Tag, AIState.Eat, food.Priority, out CharacterParams.TargetParams targetParams))
if (food.TargetParams == null)
{
targetParams.IgnoreContained = food.IgnoreContained;
food.TargetParams = targetParams;
if (AIController.AIParams.TryGetTarget(food.Tag, out TargetParams target))
{
food.TargetParams = target;
}
else if (AIController.AIParams.TryAddNewTarget(food.Tag, AIState.Eat, food.Priority, out TargetParams targetParams))
{
food.TargetParams = targetParams;
}
if (food.TargetParams != null)
{
food.TargetParams.State = AIState.Eat;
food.TargetParams.Priority = food.Priority;
food.TargetParams.IgnoreContained = food.IgnoreContained;
}
}
}
else if (food.TargetParams != null)
{
AiController.AIParams.RemoveTarget(food.TargetParams);
AIController.AIParams.RemoveTarget(food.TargetParams);
food.TargetParams = null;
}
}
@@ -116,10 +116,10 @@ namespace Barotrauma
}
// accept only the highest priority order
if (CurrentOrder != null && OrderedCharacter.GetCurrentOrderWithTopPriority() != CurrentOrder)
if (CurrentOrder == null || OrderedCharacter.GetCurrentOrderWithTopPriority() != CurrentOrder)
{
#if DEBUG
ShipCommandManager.ShipCommandLog($"Order {CurrentOrder.Name} did not match current order for character {OrderedCharacter} in {this}");
ShipCommandManager.ShipCommandLog($"{this} is no longer the top priority of {OrderedCharacter}, considering the issue unattended.");
#endif
return false;
}
@@ -356,7 +356,7 @@ namespace Barotrauma
ShipIssueWorkers.Add(new ShipIssueWorkerSteer(this, order));
}
foreach (Item item in CommandedSubmarine.GetItems(true).FindAll(i => i.HasTag("turret")))
foreach (Item item in CommandedSubmarine.GetItems(true).FindAll(i => i.HasTag("turret") && !i.HasTag("hardpoint")))
{
var order = new Order(OrderPrefab.Prefabs["operateweapons"], item, item.GetComponent<Turret>());
ShipIssueWorkers.Add(new ShipIssueWorkerOperateWeapons(this, order));