Unstable v0.10.6.0 (October 13th 2020)
This commit is contained in:
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze }
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze, Follow }
|
||||
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
|
||||
@@ -48,13 +48,11 @@ namespace Barotrauma
|
||||
private float attackLimbResetTimer;
|
||||
|
||||
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0;
|
||||
public float CombatStrength => Character.Params.AI.CombatStrength;
|
||||
private float Sight => Character.Params.AI.Sight;
|
||||
private float Hearing => Character.Params.AI.Hearing;
|
||||
private float FleeHealthThreshold => Character.Params.AI.FleeHealthThreshold;
|
||||
private float AggressionGreed => Character.Params.AI.AggressionGreed;
|
||||
private float AggressionHurt => Character.Params.AI.AggressionHurt;
|
||||
private bool AggressiveBoarding => Character.Params.AI.AggressiveBoarding;
|
||||
public float CombatStrength => AIParams.CombatStrength;
|
||||
private float Sight => AIParams.Sight;
|
||||
private float Hearing => AIParams.Hearing;
|
||||
private float FleeHealthThreshold => AIParams.FleeHealthThreshold;
|
||||
private bool AggressiveBoarding => AIParams.AggressiveBoarding;
|
||||
|
||||
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
|
||||
|
||||
@@ -90,7 +88,6 @@ namespace Barotrauma
|
||||
|
||||
private readonly float priorityFearIncreasement = 2;
|
||||
private readonly float memoryFadeTime = 0.5f;
|
||||
private readonly float avoidTime = 3;
|
||||
|
||||
private float avoidTimer;
|
||||
private float observeTimer;
|
||||
@@ -241,6 +238,10 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "dead";
|
||||
}
|
||||
else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner)
|
||||
{
|
||||
targetingTag = "owner";
|
||||
}
|
||||
else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP))
|
||||
{
|
||||
targetingTag = tP.Tag;
|
||||
@@ -384,7 +385,7 @@ namespace Barotrauma
|
||||
{
|
||||
updateTargetsTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
else if (avoidTimer <= 0)
|
||||
{
|
||||
CharacterParams.TargetParams targetingParams = null;
|
||||
UpdateTargets(Character, out targetingParams);
|
||||
@@ -393,11 +394,7 @@ namespace Barotrauma
|
||||
UpdateWallTarget();
|
||||
}
|
||||
updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f);
|
||||
if (avoidTimer > 0)
|
||||
{
|
||||
State = AIState.Escape;
|
||||
}
|
||||
else if (SelectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
}
|
||||
@@ -502,17 +499,21 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case AIState.Protect:
|
||||
case AIState.Follow:
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead)
|
||||
if (State == AIState.Protect)
|
||||
{
|
||||
// Attack the character that attacked the target we are protecting
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
return;
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead)
|
||||
{
|
||||
// Attack the character that attacked the target we are protecting
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
@@ -568,6 +569,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: doesn't work right here
|
||||
FaceTarget(SelectedAiTarget.Entity);
|
||||
}
|
||||
observeTimer -= deltaTime;
|
||||
@@ -592,7 +594,6 @@ namespace Barotrauma
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
PetBehavior?.Update(deltaTime);
|
||||
}
|
||||
IsSteeringThroughGap = false;
|
||||
if (SwarmBehavior != null)
|
||||
@@ -1454,7 +1455,8 @@ namespace Barotrauma
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
|
||||
if (!isFriendly)
|
||||
{
|
||||
SelectTarget(attacker.AiTarget);
|
||||
@@ -1527,7 +1529,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt;
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
bool retaliate = !isFriendly && SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine;
|
||||
@@ -1552,12 +1554,14 @@ namespace Barotrauma
|
||||
}
|
||||
else if (avoidGunFire)
|
||||
{
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
}
|
||||
if (Character.HealthPercentage <= FleeHealthThreshold)
|
||||
{
|
||||
avoidTimer = Rand.Range(15, 30);
|
||||
State = AIState.Flee;
|
||||
avoidTimer = AIParams.MinFleeTime * Rand.Range(0.75f, 1.25f);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
}
|
||||
}
|
||||
@@ -1587,7 +1591,7 @@ namespace Barotrauma
|
||||
if (damageTarget.Health > 0)
|
||||
{
|
||||
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
|
||||
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AggressionGreed;
|
||||
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1654,10 +1658,13 @@ namespace Barotrauma
|
||||
if (!item.Removed && item.body != null)
|
||||
{
|
||||
float itemBodyExtent = item.body.GetMaxExtent() * 2;
|
||||
if (limbDiff.LengthSquared() < itemBodyExtent * itemBodyExtent)
|
||||
if (Math.Abs(limbDiff.X) < itemBodyExtent &&
|
||||
Math.Abs(limbDiff.Y) < Character.AnimController.Collider.GetMaxExtent() + Character.AnimController.ColliderHeightFromFloor)
|
||||
{
|
||||
item.body.LinearVelocity *= 0.9f;
|
||||
item.body.LinearVelocity -= limbDiff * 0.25f;
|
||||
|
||||
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime);
|
||||
item.body.ApplyForce(-limbDiff * item.body.Mass);
|
||||
|
||||
if (item.Condition <= 0.0f)
|
||||
{
|
||||
@@ -1697,15 +1704,40 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
|
||||
if (!MathUtils.IsValid(dir))
|
||||
if (Character.CurrentHull != null && PathSteering != null)
|
||||
{
|
||||
return;
|
||||
// Inside
|
||||
Character targetCharacter = SelectedAiTarget.Entity as Character;
|
||||
if ((Character.AnimController.InWater || !Character.AnimController.CanWalk) &&
|
||||
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity)))
|
||||
{
|
||||
// Steer towards the target if in the same room and swimming
|
||||
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
|
||||
if (MathUtils.IsValid(dir))
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, dir);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use path finding
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 2);
|
||||
if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
// Can't reach
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5);
|
||||
if (Character.AnimController.InWater)
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
// Outside
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5);
|
||||
if (Character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1748,6 +1780,10 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "dead";
|
||||
}
|
||||
else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner)
|
||||
{
|
||||
targetingTag = "owner";
|
||||
}
|
||||
else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP))
|
||||
{
|
||||
targetingTag = tP.Tag;
|
||||
@@ -1992,9 +2028,16 @@ namespace Barotrauma
|
||||
if (targetingTag == null) { continue; }
|
||||
var targetParams = GetTargetParams(targetingTag);
|
||||
if (targetParams == null) { continue; }
|
||||
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine && targetParams.State == AIState.Observe)
|
||||
if (targetParams.State == AIState.Observe || targetParams.State == AIState.Eat)
|
||||
{
|
||||
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
// Don't allow to target characters that are inside a different submarine / outside when we are inside.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null)
|
||||
{
|
||||
// Don't allow to observe characters that are inside a different submarine / outside when we are inside.
|
||||
continue;
|
||||
}
|
||||
valueModifier *= targetParams.Priority;
|
||||
@@ -2066,6 +2109,17 @@ namespace Barotrauma
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (Character.CurrentHull != null && targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe)
|
||||
{
|
||||
// Ignore targets that cannot see
|
||||
if (!VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
if (targetCharacter.Submarine != null)
|
||||
|
||||
@@ -1111,7 +1111,10 @@ namespace Barotrauma
|
||||
public bool TakeItem(Item item, Inventory targetInventory, bool equip, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
|
||||
{
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
if (item.ParentInventory is ItemInventory itemInventory)
|
||||
{
|
||||
if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; }
|
||||
}
|
||||
if (equip)
|
||||
{
|
||||
int targetSlot = -1;
|
||||
|
||||
@@ -190,6 +190,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
pathFinder.InsideSubmarine = character.Submarine != null;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1;
|
||||
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
@@ -343,7 +344,7 @@ namespace Barotrauma
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
else if (!canClimb || character.AnimController.InWater)
|
||||
else if (character.AnimController.InWater)
|
||||
{
|
||||
// If the character is underwater, we don't need the ladders anymore
|
||||
if (character.IsClimbing && isDiving)
|
||||
@@ -370,7 +371,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsNextLadderSameAsCurrent)
|
||||
else if (!canClimb || !IsNextLadderSameAsCurrent)
|
||||
{
|
||||
// Walking horizontally
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
@@ -378,6 +379,8 @@ namespace Barotrauma
|
||||
Vector2 velocity = collider.LinearVelocity;
|
||||
// If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high.
|
||||
float minHeight = 1;
|
||||
// If the character is very thin, without a min value, it would often fail to reach the waypoints, because the horizontal distance is too small.
|
||||
float minWidth = 0.17f;
|
||||
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
|
||||
float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight);
|
||||
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
|
||||
@@ -386,7 +389,7 @@ namespace Barotrauma
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken;
|
||||
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
|
||||
float targetDistance = collider.radius * margin;
|
||||
float targetDistance = Math.Max(collider.radius * margin, minWidth);
|
||||
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && !blockedByDoor)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
|
||||
+6
-2
@@ -204,11 +204,15 @@ namespace Barotrauma
|
||||
{
|
||||
// Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again.
|
||||
// If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub.
|
||||
if (currentHull != null && goToObjective != null)
|
||||
// If the target hull is inside a submarine that is not our main sub, just ignore it normally when it cannot be reached. This happens with outposts.
|
||||
if (goToObjective != null)
|
||||
{
|
||||
if (goToObjective.Target is Hull hull)
|
||||
{
|
||||
HumanAIController.UnreachableHulls.Add(hull);
|
||||
if (currentHull != null || !Submarine.MainSubs.Contains(hull.Submarine))
|
||||
{
|
||||
HumanAIController.UnreachableHulls.Add(hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
|
||||
+4
@@ -257,6 +257,10 @@ namespace Barotrauma
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
if (item.ParentInventory is ItemInventory itemInventory)
|
||||
{
|
||||
if (!itemInventory.Container.HasRequiredItems(character, addMessage: false)) { continue; }
|
||||
}
|
||||
float itemPriority = 1;
|
||||
if (GetItemPriority != null)
|
||||
{
|
||||
|
||||
+5
-2
@@ -342,7 +342,7 @@ namespace Barotrauma
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace Barotrauma
|
||||
private readonly List<PathNode> nodes;
|
||||
|
||||
public bool InsideSubmarine { get; set; }
|
||||
public bool ApplyPenaltyToOutsideNodes { get; set; }
|
||||
|
||||
public PathFinder(List<WayPoint> wayPoints, bool indoorsSteering = false)
|
||||
{
|
||||
@@ -178,7 +179,7 @@ namespace Barotrauma
|
||||
node.TempDistance = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub
|
||||
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null && InsideSubmarine) { node.TempDistance *= 10.0f; }
|
||||
if (node.Waypoint.CurrentHull == null && ApplyPenaltyToOutsideNodes) { node.TempDistance *= 10.0f; }
|
||||
|
||||
//prefer nodes that are closer to the end position
|
||||
node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f;
|
||||
@@ -231,8 +232,11 @@ namespace Barotrauma
|
||||
node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition);
|
||||
if (InsideSubmarine)
|
||||
{
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; }
|
||||
if (ApplyPenaltyToOutsideNodes)
|
||||
{
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; }
|
||||
}
|
||||
//avoid stopping at a doorway
|
||||
if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; }
|
||||
//avoid stopping at a ladder
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,6 +10,14 @@ namespace Barotrauma
|
||||
{
|
||||
class PetBehavior
|
||||
{
|
||||
public enum StatusIndicatorType
|
||||
{
|
||||
None,
|
||||
Happy,
|
||||
Sad,
|
||||
Hungry
|
||||
}
|
||||
|
||||
public float Hunger { get; set; } = 50.0f;
|
||||
public float Happiness { get; set; } = 50.0f;
|
||||
|
||||
@@ -21,6 +30,7 @@ namespace Barotrauma
|
||||
public float PlayForce { get; set; }
|
||||
|
||||
public float PlayTimer { get; set; }
|
||||
private float? unstunY { get; set; }
|
||||
|
||||
public EnemyAIController AiController { get; private set; } = null;
|
||||
|
||||
@@ -126,6 +136,7 @@ namespace Barotrauma
|
||||
public float Hunger;
|
||||
public float Happiness;
|
||||
public float Priority;
|
||||
public bool IgnoreContained;
|
||||
|
||||
public CharacterParams.TargetParams TargetParams = null;
|
||||
}
|
||||
@@ -159,7 +170,11 @@ namespace Barotrauma
|
||||
case "eat":
|
||||
Food food = new Food
|
||||
{
|
||||
Tag = subElement.GetAttributeString("tag", "")
|
||||
Tag = subElement.GetAttributeString("tag", ""),
|
||||
Hunger = subElement.GetAttributeFloat("hunger", -1),
|
||||
Happiness = subElement.GetAttributeFloat("happiness", 1),
|
||||
Priority = subElement.GetAttributeFloat("priority", 100),
|
||||
IgnoreContained = subElement.GetAttributeBool("ignorecontained", true)
|
||||
};
|
||||
string[] requiredHungerStr = subElement.GetAttributeString("requiredhunger", "0-100").Split('-');
|
||||
food.HungerRange = new Vector2(0, 100);
|
||||
@@ -168,15 +183,20 @@ namespace Barotrauma
|
||||
if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float tempF)) { food.HungerRange.X = tempF; }
|
||||
if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { food.HungerRange.Y = tempF; }
|
||||
}
|
||||
food.Hunger = subElement.GetAttributeFloat("hunger", -1);
|
||||
food.Happiness = subElement.GetAttributeFloat("happiness", 1);
|
||||
food.Priority = subElement.GetAttributeFloat("priority", 100);
|
||||
foods.Add(food);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StatusIndicatorType GetCurrentStatusIndicatorType()
|
||||
{
|
||||
if (Hunger > MaxHunger * 0.5f) { return StatusIndicatorType.Hungry; }
|
||||
if (Happiness > MaxHappiness * 0.75f) { return StatusIndicatorType.Happy; }
|
||||
if (Happiness < MaxHappiness * 0.25f) { return StatusIndicatorType.Sad; }
|
||||
return StatusIndicatorType.None;
|
||||
}
|
||||
|
||||
public void OnEat(IEnumerable<string> tags, float amount)
|
||||
{
|
||||
for (int i = 0; i < foods.Count; i++)
|
||||
@@ -203,17 +223,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void Play()
|
||||
public void Play(Character player)
|
||||
{
|
||||
if (PlayTimer > 0.0f) { return; }
|
||||
if (Owner == null) { Owner = player; }
|
||||
PlayTimer = 5.0f;
|
||||
AiController.Character.Stun = 1.0f;
|
||||
AiController.Character.IsRagdolled = true;
|
||||
Happiness += 10.0f;
|
||||
if (Happiness > MaxHappiness) { Happiness = MaxHappiness; }
|
||||
AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce);
|
||||
unstunY = AiController.Character.SimPosition.Y;
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
public string GetTagName()
|
||||
{
|
||||
if (AiController.Character.Inventory != null)
|
||||
{
|
||||
@@ -230,14 +252,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
return AiController.Character.Name;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
var character = AiController.Character;
|
||||
if (character?.Removed ?? true || character.IsDead) { return; }
|
||||
if (GameMain.NetworkMember?.IsClient ?? false) { return; } //TODO: syncing
|
||||
if (GameMain.NetworkMember?.IsClient ?? false) { return; }
|
||||
|
||||
if (Owner != null && (Owner.Removed || Owner.IsDead)) { Owner = null; }
|
||||
|
||||
Hunger += HungerIncreaseRate * deltaTime;
|
||||
|
||||
@@ -245,20 +269,45 @@ namespace Barotrauma
|
||||
|
||||
PlayTimer -= deltaTime;
|
||||
|
||||
for (int i = 0; i < foods.Count; i++)
|
||||
if (unstunY.HasValue)
|
||||
{
|
||||
if (Hunger >= foods[i].HungerRange.X && Hunger <= foods[i].HungerRange.Y)
|
||||
if (PlayTimer > 4.0f)
|
||||
{
|
||||
if (foods[i].TargetParams == null &&
|
||||
AiController.AIParams.TryAddNewTarget(foods[i].Tag, AIState.Eat, foods[i].Priority, out CharacterParams.TargetParams targetParams))
|
||||
float extent = character.AnimController.MainLimb.body.GetMaxExtent();
|
||||
if (character.SimPosition.Y < (unstunY.Value + extent * 3.0f) &&
|
||||
character.AnimController.MainLimb.body.LinearVelocity.Y < 0.0f)
|
||||
{
|
||||
foods[i].TargetParams = targetParams;
|
||||
character.IsRagdolled = false;
|
||||
unstunY = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.IsRagdolled = true;
|
||||
}
|
||||
}
|
||||
else if (foods[i].TargetParams != null)
|
||||
else
|
||||
{
|
||||
AiController.AIParams.RemoveTarget(foods[i].TargetParams);
|
||||
foods[i].TargetParams = null;
|
||||
character.IsRagdolled = false;
|
||||
unstunY = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < foods.Count; i++)
|
||||
{
|
||||
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))
|
||||
{
|
||||
targetParams.IgnoreContained = food.IgnoreContained;
|
||||
food.TargetParams = targetParams;
|
||||
}
|
||||
}
|
||||
else if (food.TargetParams != null)
|
||||
{
|
||||
AiController.AIParams.RemoveTarget(food.TargetParams);
|
||||
food.TargetParams = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +328,8 @@ namespace Barotrauma
|
||||
|
||||
if (character.SelectedBy != null)
|
||||
{
|
||||
character.Stun = 1.0f;
|
||||
character.IsRagdolled = true;
|
||||
unstunY = character.SimPosition.Y;
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemsToProduce.Count; i++)
|
||||
@@ -287,5 +337,73 @@ namespace Barotrauma
|
||||
itemsToProduce[i].Update(this, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SavePets(XElement petsElement)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.IsPet || c.IsDead) { continue; }
|
||||
if (c.Submarine?.Info.Type != SubmarineType.Player) { continue; }
|
||||
|
||||
var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior;
|
||||
if (petBehavior == null) { continue; }
|
||||
|
||||
XElement petElement = new XElement("pet",
|
||||
new XAttribute("speciesname", c.SpeciesName),
|
||||
new XAttribute("ownerid", petBehavior.Owner?.ID ?? Entity.NullEntityID),
|
||||
new XAttribute("seed", c.Seed));
|
||||
|
||||
var petBehaviorElement = new XElement("petbehavior",
|
||||
new XAttribute("hunger", petBehavior.Hunger.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("happiness", petBehavior.Happiness.ToString("G", CultureInfo.InvariantCulture)));
|
||||
petElement.Add(petBehaviorElement);
|
||||
|
||||
var healthElement = new XElement("health");
|
||||
c.CharacterHealth.Save(healthElement);
|
||||
petElement.Add(healthElement);
|
||||
|
||||
if (c.Inventory != null)
|
||||
{
|
||||
var inventoryElement = new XElement("inventory");
|
||||
c.SaveInventory(c.Inventory, inventoryElement);
|
||||
petElement.Add(inventoryElement);
|
||||
}
|
||||
|
||||
petsElement.Add(petElement);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadPets(XElement petsElement)
|
||||
{
|
||||
foreach (XElement subElement in petsElement.Elements())
|
||||
{
|
||||
string speciesName = subElement.GetAttributeString("speciesname", "");
|
||||
string seed = subElement.GetAttributeString("seed", "123");
|
||||
ushort ownerID = (ushort)subElement.GetAttributeInt("ownerid", 0);
|
||||
Vector2 spawnPos = Vector2.Zero;
|
||||
Character owner = Entity.FindEntityByID(ownerID) as Character;
|
||||
if (owner != null)
|
||||
{
|
||||
spawnPos = owner.WorldPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
var spawnPoint = WayPoint.WayPointList.Where(wp => wp.SpawnType == SpawnType.Human && wp.Submarine?.Info.Type == SubmarineType.Player).GetRandom();
|
||||
spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
||||
}
|
||||
var pet = Character.Create(speciesName, spawnPos, seed);
|
||||
var petBehavior = (pet.AIController as EnemyAIController)?.PetBehavior;
|
||||
if (petBehavior != null)
|
||||
{
|
||||
petBehavior.Owner = owner;
|
||||
var petBehaviorElement = subElement.Attribute("petbehavior");
|
||||
if (petBehaviorElement != null)
|
||||
{
|
||||
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat(50.0f);
|
||||
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat(50.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ namespace Barotrauma
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
if (!Enabled) { return; }
|
||||
if (!IsRemotePlayer && AIController is EnemyAIController enemyAi)
|
||||
{
|
||||
enemyAi.PetBehavior?.Update(deltaTime);
|
||||
}
|
||||
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated)
|
||||
{
|
||||
//don't enable simple physics on dead/incapacitated characters
|
||||
|
||||
+204
-142
@@ -429,113 +429,195 @@ namespace Barotrauma
|
||||
{
|
||||
WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
|
||||
mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 transformedMovement = reverse ? -movement : movement;
|
||||
float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
|
||||
float mainLimbAngle = 0;
|
||||
if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
|
||||
{
|
||||
mainLimbAngle = TorsoAngle.Value;
|
||||
}
|
||||
else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
|
||||
{
|
||||
mainLimbAngle = HeadAngle.Value;
|
||||
}
|
||||
mainLimbAngle *= Dir;
|
||||
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
|
||||
{
|
||||
movementAngle += MathHelper.TwoPi;
|
||||
}
|
||||
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
|
||||
{
|
||||
movementAngle -= MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
if (CurrentSwimParams.RotateTowardsMovement)
|
||||
{
|
||||
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
|
||||
}
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
|
||||
}
|
||||
}
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
if (tail != null)
|
||||
{
|
||||
float? mainLimbTargetAngle = null;
|
||||
if (mainLimb.type == LimbType.Torso)
|
||||
{
|
||||
mainLimbTargetAngle = TorsoAngle;
|
||||
}
|
||||
else if (mainLimb.type == LimbType.Head)
|
||||
{
|
||||
mainLimbTargetAngle = HeadAngle;
|
||||
}
|
||||
float torque = TailTorque;
|
||||
float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
|
||||
if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
|
||||
{
|
||||
float diff = Math.Abs(mainLimb.Rotation - tail.Rotation);
|
||||
float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
|
||||
torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
|
||||
}
|
||||
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
|
||||
if (reverse)
|
||||
Vector2 transformedMovement = reverse ? -movement : movement;
|
||||
float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2;
|
||||
float mainLimbAngle = 0;
|
||||
if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
|
||||
{
|
||||
movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
|
||||
mainLimbAngle = TorsoAngle.Value;
|
||||
}
|
||||
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
|
||||
else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
mainLimbAngle = HeadAngle.Value;
|
||||
}
|
||||
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
|
||||
mainLimbAngle *= Dir;
|
||||
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
|
||||
{
|
||||
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
movementAngle += MathHelper.TwoPi;
|
||||
}
|
||||
if (TorsoAngle.HasValue)
|
||||
while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
|
||||
movementAngle -= MathHelper.TwoPi;
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
if (CurrentSwimParams.RotateTowardsMovement)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
|
||||
}
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
|
||||
}
|
||||
}
|
||||
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque);
|
||||
}
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
|
||||
}
|
||||
}
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
bool isAngleApplied = false;
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type != LimbType.Tail) { continue; }
|
||||
if (!limb.Params.ApplyTailAngle) { continue; }
|
||||
RotateTail(limb);
|
||||
isAngleApplied = true;
|
||||
}
|
||||
if (!isAngleApplied)
|
||||
{
|
||||
RotateTail(GetLimb(LimbType.Tail));
|
||||
}
|
||||
|
||||
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
|
||||
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
|
||||
WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
|
||||
void RotateTail(Limb tail)
|
||||
{
|
||||
if (tail == null) { return; }
|
||||
float? mainLimbTargetAngle = null;
|
||||
if (mainLimb.type == LimbType.Torso)
|
||||
{
|
||||
mainLimbTargetAngle = TorsoAngle;
|
||||
}
|
||||
else if (mainLimb.type == LimbType.Head)
|
||||
{
|
||||
mainLimbTargetAngle = HeadAngle;
|
||||
}
|
||||
float torque = TailTorque;
|
||||
float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier;
|
||||
if (mainLimbTargetAngle.HasValue && maxMultiplier > 1)
|
||||
{
|
||||
float diff = Math.Abs(mainLimb.Rotation - tail.Rotation);
|
||||
float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value);
|
||||
torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset));
|
||||
}
|
||||
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
|
||||
if (reverse)
|
||||
{
|
||||
movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi);
|
||||
}
|
||||
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque);
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque);
|
||||
}
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
bool isAngleApplied = false;
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type != LimbType.Tail) { continue; }
|
||||
if (!limb.Params.ApplyTailAngle) { continue; }
|
||||
RotateTail(limb);
|
||||
isAngleApplied = true;
|
||||
}
|
||||
if (!isAngleApplied)
|
||||
{
|
||||
RotateTail(GetLimb(LimbType.Tail));
|
||||
}
|
||||
|
||||
void RotateTail(Limb tail)
|
||||
{
|
||||
if (tail != null)
|
||||
{
|
||||
tail.body.SmoothRotate(TailAngle.Value * Dir, TailTorque);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
|
||||
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
|
||||
WalkPos = MathUtils.WrapAngleTwoPi(WalkPos);
|
||||
}
|
||||
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
switch (limb.type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
|
||||
{
|
||||
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
|
||||
}
|
||||
break;
|
||||
case LimbType.Tail:
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
float waveRotation = (float)Math.Sin(WalkPos * limb.Params.SineFrequencyMultiplier);
|
||||
limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude * limb.Params.SineAmplitudeMultiplier);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
var limb = Limbs[i];
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.SteerForce <= 0.0f) { continue; }
|
||||
if (!Collider.PhysEnabled) { continue; }
|
||||
Vector2 pullPos = limb.PullJointWorldAnchorA;
|
||||
limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
|
||||
}
|
||||
|
||||
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
|
||||
if (CurrentSwimParams.UseSineMovement)
|
||||
{
|
||||
mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
|
||||
mainLimb.PullJointWorldAnchorB,
|
||||
Collider.SimPosition,
|
||||
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
|
||||
}
|
||||
else
|
||||
{
|
||||
//mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
|
||||
mainLimb.PullJointWorldAnchorB,
|
||||
Collider.SimPosition,
|
||||
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var limb in Limbs)
|
||||
@@ -543,55 +625,15 @@ namespace Barotrauma
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
|
||||
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
|
||||
}
|
||||
switch (limb.type)
|
||||
if (limb.Params.BlinkFrequency > 0)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID))
|
||||
{
|
||||
SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque);
|
||||
}
|
||||
break;
|
||||
case LimbType.Tail:
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
float waveRotation = (float)Math.Sin(WalkPos);
|
||||
limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude);
|
||||
}
|
||||
break;
|
||||
limb.Blink(deltaTime, MainLimb.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
var limb = Limbs[i];
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.SteerForce <= 0.0f) { continue; }
|
||||
if (!Collider.PhysEnabled) { continue; }
|
||||
Vector2 pullPos = limb.PullJointWorldAnchorA;
|
||||
limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
|
||||
}
|
||||
|
||||
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
|
||||
if (CurrentSwimParams.UseSineMovement)
|
||||
{
|
||||
mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep(
|
||||
mainLimb.PullJointWorldAnchorB,
|
||||
Collider.SimPosition,
|
||||
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos)));
|
||||
}
|
||||
else
|
||||
{
|
||||
//mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
mainLimb.PullJointWorldAnchorB = Vector2.Lerp(
|
||||
mainLimb.PullJointWorldAnchorB,
|
||||
Collider.SimPosition,
|
||||
mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f);
|
||||
}
|
||||
|
||||
floorY = Limbs[0].SimPosition.Y;
|
||||
floorY = Limbs[0].SimPosition.Y;
|
||||
}
|
||||
|
||||
void UpdateWalkAnim(float deltaTime)
|
||||
@@ -686,10 +728,26 @@ namespace Barotrauma
|
||||
|
||||
if (TailAngle.HasValue)
|
||||
{
|
||||
var tail = GetLimb(LimbType.Tail);
|
||||
if (tail != null)
|
||||
bool isAngleApplied = false;
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque);
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type != LimbType.Tail) { continue; }
|
||||
if (!limb.Params.ApplyTailAngle) { continue; }
|
||||
RotateTail(limb);
|
||||
isAngleApplied = true;
|
||||
}
|
||||
if (!isAngleApplied)
|
||||
{
|
||||
RotateTail(GetLimb(LimbType.Tail));
|
||||
}
|
||||
|
||||
void RotateTail(Limb tail)
|
||||
{
|
||||
if (tail != null)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +767,11 @@ namespace Barotrauma
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
|
||||
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
|
||||
}
|
||||
if (limb.Params.BlinkFrequency > 0)
|
||||
{
|
||||
limb.Blink(deltaTime, MainLimb.Rotation);
|
||||
}
|
||||
switch (limb.type)
|
||||
{
|
||||
|
||||
+16
-10
@@ -145,7 +145,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private LimbJoint shoulder;
|
||||
private LimbJoint rightShoulder, leftShoulder;
|
||||
|
||||
private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
|
||||
|
||||
@@ -241,12 +241,13 @@ namespace Barotrauma
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
if (rightHand == null) { return; }
|
||||
|
||||
shoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm);
|
||||
rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm);
|
||||
leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm);
|
||||
Vector2 localAnchorShoulder = Vector2.Zero;
|
||||
Vector2 localAnchorElbow = Vector2.Zero;
|
||||
if (shoulder != null)
|
||||
if (rightShoulder != null)
|
||||
{
|
||||
localAnchorShoulder = shoulder.LimbA.type == LimbType.RightArm ? shoulder.LocalAnchorA : shoulder.LocalAnchorB;
|
||||
localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB;
|
||||
}
|
||||
LimbJoint rightElbow = rightForearm == null ?
|
||||
GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) :
|
||||
@@ -1612,7 +1613,7 @@ namespace Barotrauma
|
||||
pullLimb.PullJointMaxForce = 5000.0f;
|
||||
targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
|
||||
|
||||
Vector2 shoulderPos = shoulder.WorldAnchorA;
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY;
|
||||
|
||||
targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength);
|
||||
@@ -1757,10 +1758,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
itemAngle = (torso.body.Rotation + holdAngle * Dir);
|
||||
itemAngle = torso.body.Rotation + holdAngle * Dir;
|
||||
}
|
||||
|
||||
Vector2 transformedHoldPos = shoulder.WorldAnchorA;
|
||||
Vector2 transformedHoldPos = rightShoulder.WorldAnchorA;
|
||||
if (itemPos == Vector2.Zero || isClimbing || usingController)
|
||||
{
|
||||
if (character.SelectedItems[0] == item)
|
||||
@@ -1781,15 +1782,17 @@ namespace Barotrauma
|
||||
if (character.SelectedItems[0] == item)
|
||||
{
|
||||
if (rightHand == null || rightHand.IsSevered) { return; }
|
||||
transformedHoldPos = rightShoulder.WorldAnchorA;
|
||||
rightHand.Disabled = true;
|
||||
}
|
||||
if (character.SelectedItems[1] == item)
|
||||
{
|
||||
if (leftHand == null || leftHand.IsSevered) { return; }
|
||||
transformedHoldPos = leftShoulder.WorldAnchorA;
|
||||
leftHand.Disabled = true;
|
||||
}
|
||||
|
||||
itemPos.X = itemPos.X * Dir;
|
||||
itemPos.X *= Dir;
|
||||
transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle));
|
||||
}
|
||||
|
||||
@@ -1858,18 +1861,21 @@ namespace Barotrauma
|
||||
|
||||
private void HandIK(Limb hand, Vector2 pos, float force = 1.0f)
|
||||
{
|
||||
if (shoulder == null) { return; }
|
||||
Vector2 shoulderPos = shoulder.WorldAnchorA;
|
||||
Vector2 shoulderPos;
|
||||
|
||||
Limb arm, forearm;
|
||||
if (hand.type == LimbType.LeftHand)
|
||||
{
|
||||
if (leftShoulder == null) { return; }
|
||||
shoulderPos = leftShoulder.WorldAnchorA;
|
||||
arm = GetLimb(LimbType.LeftArm);
|
||||
forearm = GetLimb(LimbType.LeftForearm);
|
||||
LeftHandIKPos = pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rightShoulder == null) { return; }
|
||||
shoulderPos = rightShoulder.WorldAnchorA;
|
||||
arm = GetLimb(LimbType.RightArm);
|
||||
forearm = GetLimb(LimbType.RightForearm);
|
||||
RightHandIKPos = pos;
|
||||
|
||||
@@ -216,16 +216,18 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
Limb mainLimb = GetLimb(RagdollParams.MainLimb);
|
||||
if (mainLimb == null)
|
||||
if (!IsValid(mainLimb))
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
mainLimb = torso ?? head;
|
||||
if (mainLimb == null)
|
||||
if (!IsValid(mainLimb))
|
||||
{
|
||||
mainLimb = Limbs.FirstOrDefault(l => !l.IsSevered && !l.ignoreCollisions);
|
||||
mainLimb = Limbs.FirstOrDefault(l => IsValid(l));
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.ignoreCollisions;
|
||||
return mainLimb;
|
||||
}
|
||||
}
|
||||
@@ -753,7 +755,6 @@ namespace Barotrauma
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (connectedLimbs.Contains(limb)) { continue; }
|
||||
if (!character.IsDead && !limb.CanBeSeveredAlive) { continue; }
|
||||
limb.IsSevered = true;
|
||||
if (limb.type == LimbType.RightHand)
|
||||
{
|
||||
@@ -765,7 +766,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(character.BloodDecalName))
|
||||
{
|
||||
character.CurrentHull?.AddDecal(character.BloodDecalName,
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
protected float oxygenAvailable;
|
||||
|
||||
//seed used to generate this character
|
||||
private readonly string seed;
|
||||
public readonly string Seed;
|
||||
protected Item focusedItem;
|
||||
private Character selectedCharacter, selectedBy;
|
||||
public Character LastAttacker;
|
||||
@@ -273,7 +273,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (IsPet)
|
||||
{
|
||||
return (AIController as EnemyAIController).PetBehavior.GetName();
|
||||
string petName = (AIController as EnemyAIController).PetBehavior.GetTagName();
|
||||
if (!string.IsNullOrEmpty(petName)) { return petName; }
|
||||
}
|
||||
|
||||
if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; }
|
||||
@@ -827,7 +828,7 @@ namespace Barotrauma
|
||||
{
|
||||
prefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
|
||||
this.seed = seed;
|
||||
this.Seed = seed;
|
||||
MTRandom random = new MTRandom(ToolBox.StringToInt(seed));
|
||||
|
||||
selectedItems = new Item[2];
|
||||
@@ -2178,7 +2179,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (FocusedCharacter != null && !FocusedCharacter.IsIncapacitated && IsKeyHit(InputType.Use) && FocusedCharacter.IsPet && CanInteract)
|
||||
{
|
||||
(FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play();
|
||||
(FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play(this);
|
||||
}
|
||||
else if (FocusedCharacter != null && IsKeyHit(InputType.Health) && FocusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(FocusedCharacter, 160f, false))
|
||||
{
|
||||
@@ -2918,7 +2919,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
// Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head)
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: AIController == null || AIController is HumanAIController);
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: attacker.IsHuman || attacker.IsPlayer);
|
||||
|
||||
return attackResult;
|
||||
}
|
||||
@@ -2944,7 +2945,8 @@ namespace Barotrauma
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
if (!joint.CanBeSevered) { continue; }
|
||||
if (joint.LimbA != targetLimb && joint.LimbB != targetLimb) { continue; }
|
||||
// Limb A is where we usually create the joints from. Let's not allow severing when the "parent" limb is hit, or the head can pop off when we hit the torso, for example.
|
||||
if (joint.LimbB != targetLimb) { continue; }
|
||||
float probability = severLimbsProbability;
|
||||
if (!IsDead)
|
||||
{
|
||||
@@ -3225,6 +3227,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
|
||||
IsDead = true;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnDeath, 1.0f);
|
||||
|
||||
@@ -768,6 +768,18 @@ namespace Barotrauma
|
||||
{
|
||||
attack.UpdateCoolDown(deltaTime);
|
||||
}
|
||||
|
||||
if (Params.BlinkFrequency > 0)
|
||||
{
|
||||
if (blinkTimer > -TotalBlinkDurationOut)
|
||||
{
|
||||
blinkTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
blinkTimer = Params.BlinkFrequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
@@ -1039,6 +1051,45 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float blinkTimer;
|
||||
private float blinkPhase;
|
||||
|
||||
private float TotalBlinkDurationOut => Params.BlinkDurationOut + Params.BlinkHoldTime;
|
||||
|
||||
public void Blink(float deltaTime, float referenceRotation)
|
||||
{
|
||||
if (blinkTimer > -TotalBlinkDurationOut)
|
||||
{
|
||||
blinkPhase -= deltaTime;
|
||||
if (blinkPhase > 0)
|
||||
{
|
||||
// in
|
||||
float t = ToolBox.GetEasing(Params.BlinkTransitionIn, MathUtils.InverseLerp(1, 0, blinkPhase / Params.BlinkDurationIn));
|
||||
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Abs(blinkPhase) < Params.BlinkHoldTime)
|
||||
{
|
||||
// hold
|
||||
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce, wrapAngle: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// out
|
||||
float t = ToolBox.GetEasing(Params.BlinkTransitionOut, MathUtils.InverseLerp(0, 1, -blinkPhase / TotalBlinkDurationOut));
|
||||
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// out
|
||||
blinkPhase = Params.BlinkDurationIn;
|
||||
body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce, wrapAngle: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
body?.Remove();
|
||||
|
||||
+1
-1
@@ -305,7 +305,7 @@ namespace Barotrauma
|
||||
instance.Load(fullPath, speciesName);
|
||||
anims.Add(fileName, instance);
|
||||
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
||||
return instance as T;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public bool Serialize() => base.Serialize();
|
||||
|
||||
@@ -474,6 +474,12 @@ namespace Barotrauma
|
||||
[Serialize(true, true, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable]
|
||||
public bool AvoidGunfire { get; private set; }
|
||||
|
||||
[Serialize(3f, true, description: "How long the creature avoids gunfire. Also used when the creature is unlatched."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
public float AvoidTime { get; private set; }
|
||||
|
||||
[Serialize(20f, true, description: "How long the creature flees before returning to normal state. When the creature sees the target or is being chased, it will always flee, if it's in the flee state."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
public float MinFleeTime { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()]
|
||||
public bool AggressiveBoarding { get; private set; }
|
||||
|
||||
@@ -580,6 +586,9 @@ namespace Barotrauma
|
||||
[Serialize(0f, true, description: "Generic timer that can be used for different purposes depending on the state. E.g. in Observe state this defines how long the character in general keeps staring the targets (Some random is always applied)."), Editable]
|
||||
public float Timer { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the target be ignored if it's inside a container/inventory. Only affects items."), Editable]
|
||||
public bool IgnoreContained { get; set; }
|
||||
|
||||
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
|
||||
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
|
||||
|
||||
+48
-12
@@ -77,7 +77,7 @@ namespace Barotrauma
|
||||
[Serialize(LimbType.Torso, true), Editable]
|
||||
public LimbType MainLimb { get; set; }
|
||||
|
||||
private static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>();
|
||||
private readonly static Dictionary<string, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<string, Dictionary<string, RagdollParams>>();
|
||||
|
||||
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
|
||||
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
||||
@@ -546,14 +546,17 @@ namespace Barotrauma
|
||||
[Serialize(LimbType.None, true, description: "The limb type affects many things, like the animations. Torso or Head are considered as the main limbs. Every character should have at least one Torso or Head."), Editable()]
|
||||
public LimbType Type { get; set; }
|
||||
|
||||
[Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360)]
|
||||
public float SpriteOrientation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The orientation of the sprite as drawn on the sprite sheet (in radians).
|
||||
/// </summary>
|
||||
public float GetSpriteOrientation() => MathHelper.ToRadians(float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation);
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Notes { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable]
|
||||
public float Scale { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()]
|
||||
public bool Flip { get; set; }
|
||||
|
||||
@@ -566,8 +569,8 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "Disable drawing for this limb."), Editable()]
|
||||
public bool Hide { get; set; }
|
||||
|
||||
[Serialize(1f, true, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)]
|
||||
public float AttackPriority { get; set; }
|
||||
[Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)]
|
||||
public float SpriteOrientation { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)]
|
||||
public float SteerForce { get; set; }
|
||||
@@ -590,6 +593,9 @@ namespace Barotrauma
|
||||
[Serialize(7f, true, description: "Increasing the damping makes the limb stop rotating more quickly."), Editable]
|
||||
public float AngularDamping { get; set; }
|
||||
|
||||
[Serialize(1f, true, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)]
|
||||
public float AttackPriority { get; set; }
|
||||
|
||||
[Serialize("0, 0", true, description: "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot."), Editable()]
|
||||
public Vector2 PullPos { get; set; }
|
||||
|
||||
@@ -602,18 +608,12 @@ namespace Barotrauma
|
||||
[Serialize("0, 0", true, description: "Relative offset for the mouth position (starting from the center). Only applicable for LimbType.Head. Used for eating."), Editable(DecimalCount = 2, MinValueFloat = -10f, MaxValueFloat = 10f)]
|
||||
public Vector2 MouthPos { get; set; }
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Notes { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float ConstantTorque { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float ConstantAngle { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable]
|
||||
public float Scale { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float AttackForceMultiplier { get; set; }
|
||||
|
||||
@@ -624,6 +624,42 @@ namespace Barotrauma
|
||||
[Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)]
|
||||
public float SeveredFadeOutTime { get; set; } = 10.0f;
|
||||
|
||||
[Serialize(false, true, description: "Only applied when the limb is of type Tail. If none of the tails have been defined to use the angle and an angle is defined in the animation parameters, the first tail limb is used."), Editable]
|
||||
public bool ApplyTailAngle { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable(ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float SineFrequencyMultiplier { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable(ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float SineAmplitudeMultiplier { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0, 100, ValueStep = 1, DecimalCount = 1)]
|
||||
public float BlinkFrequency { get; set; }
|
||||
|
||||
[Serialize(0.2f, true), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
|
||||
public float BlinkDurationIn { get; set; }
|
||||
|
||||
[Serialize(0.5f, true), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)]
|
||||
public float BlinkDurationOut { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(0, 10, ValueStep = 1, DecimalCount = 2)]
|
||||
public float BlinkHoldTime { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
|
||||
public float BlinkRotationIn { get; set; }
|
||||
|
||||
[Serialize(45f, true), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)]
|
||||
public float BlinkRotationOut { get; set; }
|
||||
|
||||
[Serialize(50f, true), Editable]
|
||||
public float BlinkForce { get; set; }
|
||||
|
||||
[Serialize(TransitionMode.Linear, true), Editable]
|
||||
public TransitionMode BlinkTransitionIn { get; private set; }
|
||||
|
||||
[Serialize(TransitionMode.Linear, true), Editable]
|
||||
public TransitionMode BlinkTransitionOut { get; private set; }
|
||||
|
||||
// Non-editable ->
|
||||
// TODO: make read-only
|
||||
[Serialize(0, true)]
|
||||
|
||||
@@ -592,13 +592,13 @@ namespace Barotrauma
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
|
||||
{
|
||||
//crawler inside the sub adds 0.1f to enemy danger, mantis 0.25f
|
||||
enemyDanger += enemyAI.CombatStrength / 1000.0f;
|
||||
enemyDanger += enemyAI.CombatStrength / 100.0f;
|
||||
}
|
||||
else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
//enemy outside and targeting the sub or something in it
|
||||
//moloch adds 0.24 to enemy danger, a crawler 0.02
|
||||
enemyDanger += enemyAI.CombatStrength / 2000.0f;
|
||||
enemyDanger += enemyAI.CombatStrength / 1000.0f;
|
||||
}
|
||||
}
|
||||
enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f);
|
||||
@@ -654,12 +654,12 @@ namespace Barotrauma
|
||||
if (targetIntensity > currentIntensity)
|
||||
{
|
||||
//25 seconds for intensity to go from 0.0 to 1.0
|
||||
currentIntensity = MathHelper.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity);
|
||||
currentIntensity = Math.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
//400 seconds for intensity to go from 1.0 to 0.0
|
||||
currentIntensity = MathHelper.Max(0.0025f * IntensityUpdateInterval, targetIntensity);
|
||||
currentIntensity = Math.Max(currentIntensity - 0.0025f * IntensityUpdateInterval, targetIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Barotrauma
|
||||
|
||||
public CampaignMetadata CampaignMetadata;
|
||||
|
||||
protected XElement petsElement;
|
||||
|
||||
public enum TransitionType
|
||||
{
|
||||
None,
|
||||
|
||||
@@ -282,6 +282,11 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted.");
|
||||
return;
|
||||
}
|
||||
if (SubmarineInfo.SubmarineElement.Elements().Count() == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, saved submarine is empty. The submarine file may be corrupted.");
|
||||
return;
|
||||
}
|
||||
|
||||
LevelData = levelData;
|
||||
|
||||
|
||||
@@ -6,18 +6,20 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Throwable : Holdable
|
||||
{
|
||||
private float throwForce, throwPos;
|
||||
private float throwPos;
|
||||
private bool throwing, throwDone;
|
||||
|
||||
private bool midAir;
|
||||
|
||||
[Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")]
|
||||
public float ThrowForce
|
||||
public Character CurrentThrower
|
||||
{
|
||||
get { return throwForce; }
|
||||
set { throwForce = value; }
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")]
|
||||
public float ThrowForce { get; set; }
|
||||
|
||||
public Throwable(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -60,6 +62,21 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (item.body.LinearVelocity.LengthSquared() < 0.01f)
|
||||
{
|
||||
CurrentThrower = null;
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnImpact))
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
|
||||
{
|
||||
statusEffect.SetUser(null);
|
||||
}
|
||||
}
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnBroken))
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
|
||||
{
|
||||
statusEffect.SetUser(null);
|
||||
}
|
||||
}
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
|
||||
midAir = false;
|
||||
}
|
||||
@@ -117,9 +134,24 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
Character thrower = picker;
|
||||
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
||||
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
CurrentThrower = picker;
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnImpact))
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
|
||||
{
|
||||
statusEffect.SetUser(CurrentThrower);
|
||||
}
|
||||
}
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnBroken))
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
|
||||
{
|
||||
statusEffect.SetUser(CurrentThrower);
|
||||
}
|
||||
}
|
||||
|
||||
item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
||||
item.body.ApplyLinearImpulse(throwVector * ThrowForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
//disable platform collisions until the item comes back to rest again
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
@@ -135,12 +167,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, thrower.ID });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, CurrentThrower.ID });
|
||||
}
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower, user: thrower);
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
|
||||
}
|
||||
throwing = false;
|
||||
}
|
||||
|
||||
@@ -349,6 +349,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
if (item.Submarine == null || !item.Submarine.Loading)
|
||||
{
|
||||
SpawnAlwaysContainedItems();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (itemIds != null)
|
||||
@@ -361,7 +369,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
itemIds = null;
|
||||
}
|
||||
SpawnAlwaysContainedItems();
|
||||
}
|
||||
|
||||
private void SpawnAlwaysContainedItems()
|
||||
{
|
||||
if (SpawnWithId.Length > 0)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
|
||||
@@ -375,6 +387,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void ShallowRemoveComponentSpecific()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1658,10 +1658,14 @@ namespace Barotrauma
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
if (!Prefab.CanFlipX) { return; }
|
||||
|
||||
//call the base method even if the item can't flip, to handle repositioning when flipping the whole sub
|
||||
base.FlipX(relativeToSub);
|
||||
|
||||
if (!Prefab.CanFlipX)
|
||||
{
|
||||
flippedX = false;
|
||||
return;
|
||||
}
|
||||
#if CLIENT
|
||||
if (Prefab.CanSpriteFlipX)
|
||||
{
|
||||
@@ -1677,10 +1681,15 @@ namespace Barotrauma
|
||||
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
if (!Prefab.CanFlipY) { return; }
|
||||
|
||||
//call the base method even if the item can't flip, to handle repositioning when flipping the whole sub
|
||||
base.FlipY(relativeToSub);
|
||||
|
||||
if (!Prefab.CanFlipY)
|
||||
{
|
||||
flippedY = false;
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (Prefab.CanSpriteFlipY)
|
||||
{
|
||||
|
||||
@@ -1340,6 +1340,9 @@ namespace Barotrauma
|
||||
decalsCleaned = true;
|
||||
#if SERVER
|
||||
decalUpdatePending = true;
|
||||
#elif CLIENT
|
||||
pendingDecalUpdates.Add(decal);
|
||||
networkUpdatePending = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,7 +833,7 @@ namespace Barotrauma
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(StartLocation);
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
//observable collection because some entities may need to be notified when the collection is modified
|
||||
public readonly ObservableCollection<MapEntity> linkedTo = new ObservableCollection<MapEntity>();
|
||||
|
||||
private bool flippedX, flippedY;
|
||||
protected bool flippedX, flippedY;
|
||||
public bool FlippedX { get { return flippedX; } }
|
||||
public bool FlippedY { get { return flippedY; } }
|
||||
|
||||
@@ -534,7 +534,7 @@ namespace Barotrauma
|
||||
public virtual void FlipX(bool relativeToSub)
|
||||
{
|
||||
flippedX = !flippedX;
|
||||
if (!relativeToSub || Submarine == null) return;
|
||||
if (!relativeToSub || Submarine == null) { return; }
|
||||
|
||||
Vector2 relative = WorldPosition - Submarine.WorldPosition;
|
||||
relative.Y = 0.0f;
|
||||
@@ -548,7 +548,7 @@ namespace Barotrauma
|
||||
public virtual void FlipY(bool relativeToSub)
|
||||
{
|
||||
flippedY = !flippedY;
|
||||
if (!relativeToSub || Submarine == null) return;
|
||||
if (!relativeToSub || Submarine == null) { return; }
|
||||
|
||||
Vector2 relative = WorldPosition - Submarine.WorldPosition;
|
||||
relative.X = 0.0f;
|
||||
|
||||
@@ -415,7 +415,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SerializableProperty.TrySetValue", e);
|
||||
DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,9 +733,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targets.FirstOrDefault(t => t is MapEntity) is MapEntity targetEntity && !targetEntity.Removed)
|
||||
var targetLimb = targets.FirstOrDefault(t => t is Limb) as Limb;
|
||||
if (targetLimb != null && !targetLimb.Removed)
|
||||
{
|
||||
position = targetEntity.WorldPosition;
|
||||
position = targetLimb.WorldPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user