Unstable v0.1300.0.1
This commit is contained in:
@@ -232,8 +232,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsWithinSector(Vector2 worldPosition)
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) return true;
|
||||
|
||||
if (sectorRad >= MathHelper.TwoPi) { return true; }
|
||||
Vector2 diff = worldPosition - WorldPosition;
|
||||
return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace Barotrauma
|
||||
private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget));
|
||||
private string GetTargetingTag(AITarget aiTarget)
|
||||
{
|
||||
if (aiTarget.Entity == null) { return null; }
|
||||
if (aiTarget?.Entity == null) { return null; }
|
||||
string targetingTag = null;
|
||||
if (aiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
@@ -377,6 +377,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (DisableEnemyAI) { return; }
|
||||
base.Update(deltaTime);
|
||||
UpdateTriggers(deltaTime);
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
@@ -462,7 +463,7 @@ namespace Barotrauma
|
||||
{
|
||||
updateTargetsTimer -= deltaTime;
|
||||
}
|
||||
else if (avoidTimer <= 0)
|
||||
else if (avoidTimer <= 0 || activeTriggers.Any() && returnTimer <= 0)
|
||||
{
|
||||
CharacterParams.TargetParams targetingParams = null;
|
||||
UpdateTargets(Character, out targetingParams);
|
||||
@@ -1448,8 +1449,12 @@ namespace Barotrauma
|
||||
break;
|
||||
case AttackPattern.Circle:
|
||||
if (IsCoolDownRunning) { break; }
|
||||
if (IsAttackRunning) { break; }
|
||||
if (IsAttackRunning && CirclePhase != CirclePhase.Strike) { break; }
|
||||
if (selectedTargetingParams == null) { break; }
|
||||
var targetSub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (targetSub == null) { break; }
|
||||
float subSize = Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2;
|
||||
float sqrDistToSub = Vector2.DistanceSquared(WorldPosition, targetSub.WorldPosition);
|
||||
switch (CirclePhase)
|
||||
{
|
||||
case CirclePhase.Start:
|
||||
@@ -1471,22 +1476,31 @@ namespace Barotrauma
|
||||
circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f)));
|
||||
canAttack = false;
|
||||
aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression);
|
||||
CirclePhase = Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance) ? CirclePhase.CloseIn : CirclePhase.FallBack;
|
||||
if (targetSub.Borders.Width < 1000)
|
||||
{
|
||||
breakCircling = true;
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.FallBack;
|
||||
}
|
||||
else
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
}
|
||||
break;
|
||||
case CirclePhase.CloseIn:
|
||||
var sub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (sub == null)
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
break;
|
||||
}
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(sub.Velocity))
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
|
||||
{
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
}
|
||||
else if (!breakCircling && Vector2.DistanceSquared(WorldPosition, attackWorldPos) <= MathUtils.Pow2(circleFallbackDistance - 1000) &&
|
||||
sub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
}
|
||||
@@ -1494,23 +1508,17 @@ namespace Barotrauma
|
||||
break;
|
||||
case CirclePhase.FallBack:
|
||||
bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true);
|
||||
if (isBlocked || Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance))
|
||||
if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case CirclePhase.Advance:
|
||||
var targetSub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (targetSub == null)
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
break;
|
||||
}
|
||||
Vector2 subSpeed = targetSub.Velocity;
|
||||
float requiredDistMultiplier = 1;
|
||||
// If the target sub is moving fast, just steer towards the target until close enough to strike
|
||||
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || distance > selectedTargetingParams.CircleStartDistance + 1000)
|
||||
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f))
|
||||
{
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
@@ -1532,7 +1540,7 @@ namespace Barotrauma
|
||||
// When the offset position is outside of the sub it happens that the creature sometimes reaches the target point,
|
||||
// which makes it continue circling around the point (as supposed)
|
||||
// But when there is some offset and the offset is too near, this is not what we want.
|
||||
if (targetSub.Borders.ContainsWorld(attackWorldPos + ConvertUnits.ToDisplayUnits(circleOffset)))
|
||||
if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
@@ -1646,15 +1654,15 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
}
|
||||
else
|
||||
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
}
|
||||
}
|
||||
else if (IsAttackRunning)
|
||||
{
|
||||
AttackingLimb.attack.ResetAttackTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Limb> attackLimbs = new List<Limb>();
|
||||
@@ -2060,6 +2068,7 @@ namespace Barotrauma
|
||||
targetValue = 0;
|
||||
selectedTargetMemory = null;
|
||||
targetingParams = null;
|
||||
bool isAnyTargetClose = false;
|
||||
|
||||
foreach (AITarget aiTarget in AITarget.List)
|
||||
{
|
||||
@@ -2163,6 +2172,14 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (door == null)
|
||||
{
|
||||
// Ignore items inside ruins, unless we are in the same hull. We can't target the ruin walls.
|
||||
if (item.Submarine == null && item.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var prio in AIParams.Targets)
|
||||
{
|
||||
if (item.HasTag(prio.Tag))
|
||||
@@ -2379,18 +2396,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
|
||||
Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition;
|
||||
float dist = toTarget.Length();
|
||||
|
||||
float nonModifiedDist = dist;
|
||||
//if the target has been within range earlier, the character will notice it more easily
|
||||
if (targetMemories.ContainsKey(aiTarget))
|
||||
{
|
||||
dist *= 0.9f;
|
||||
}
|
||||
|
||||
if (!CanPerceive(aiTarget, dist)) { continue; }
|
||||
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
|
||||
|
||||
//if the target is very close, the distance doesn't make much difference
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
@@ -2405,14 +2420,26 @@ namespace Barotrauma
|
||||
|
||||
if (targetParams.AttackPattern == AttackPattern.Circle)
|
||||
{
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null)
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose)
|
||||
{
|
||||
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
// Prioritize targets that are near the horizontal center of the sub
|
||||
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
|
||||
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else
|
||||
{
|
||||
dist *= 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
|
||||
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid)
|
||||
{
|
||||
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2486,11 +2513,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (targetCharacter.Submarine == null && Character.Submarine == null)
|
||||
{
|
||||
// Ignore the target when it's far enough and blocked by the level geometry, because the steering avoidance probably can't get us to the target.
|
||||
if (dist > Math.Clamp(ConvertUnits.ToDisplayUnits(colliderLength) * 10, 1000, 5000))
|
||||
{
|
||||
if (Submarine.PickBodies(SimPosition, targetCharacter.SimPosition, collisionCategory: Physics.CollisionLevel).Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newTarget = aiTarget;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = valueModifier;
|
||||
targetingParams = targetParams;
|
||||
if (!isAnyTargetClose)
|
||||
{
|
||||
isAnyTargetClose = ConvertUnits.ToDisplayUnits(colliderLength) > nonModifiedDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2619,7 +2661,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null && selectedTargetingParams?.AttackPattern == AttackPattern.Straight)
|
||||
{
|
||||
if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine ||
|
||||
closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine)
|
||||
@@ -2753,6 +2795,44 @@ namespace Barotrauma
|
||||
private readonly float stateResetCooldown = 10;
|
||||
private float stateResetTimer;
|
||||
private bool isStateChanged;
|
||||
private readonly Dictionary<AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<AITrigger, CharacterParams.TargetParams>();
|
||||
private readonly HashSet<AITrigger> inactiveTriggers = new HashSet<AITrigger>();
|
||||
|
||||
public void LaunchTrigger(AITrigger trigger)
|
||||
{
|
||||
if (trigger.IsTriggered) { return; }
|
||||
if (activeTriggers.ContainsKey(trigger)) { return; }
|
||||
if (activeTriggers.ContainsValue(selectedTargetingParams))
|
||||
{
|
||||
if (!trigger.AllowToOverride) { return; }
|
||||
var existingTrigger = activeTriggers.FirstOrDefault(kvp => kvp.Value == selectedTargetingParams && kvp.Key.AllowToBeOverridden);
|
||||
if (existingTrigger.Key == null) { return; }
|
||||
activeTriggers.Remove(existingTrigger.Key);
|
||||
}
|
||||
trigger.Launch();
|
||||
activeTriggers.Add(trigger, selectedTargetingParams);
|
||||
ChangeParams(selectedTargetingParams, trigger.State);
|
||||
}
|
||||
|
||||
private void UpdateTriggers(float deltaTime)
|
||||
{
|
||||
foreach (var triggerObject in activeTriggers)
|
||||
{
|
||||
AITrigger trigger = triggerObject.Key;
|
||||
trigger.UpdateTimer(deltaTime);
|
||||
if (!trigger.IsActive)
|
||||
{
|
||||
trigger.Reset();
|
||||
ResetParams(triggerObject.Value);
|
||||
inactiveTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
foreach (AITrigger trigger in inactiveTriggers)
|
||||
{
|
||||
activeTriggers.Remove(trigger);
|
||||
}
|
||||
inactiveTriggers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the target's state to the original value defined in the xml.
|
||||
@@ -2768,11 +2848,7 @@ namespace Barotrauma
|
||||
tempParams.Values.ForEach(t => AIParams.RemoveTarget(t));
|
||||
tempParams.Remove(tag);
|
||||
}
|
||||
targetParams.Reset();
|
||||
ResetAITarget();
|
||||
// Enforce the idle state so that we don't keep following the target if there's one
|
||||
State = AIState.Idle;
|
||||
PreviousState = AIState.Idle;
|
||||
ResetParams(targetParams);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -2784,6 +2860,27 @@ namespace Barotrauma
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
|
||||
private void ChangeParams(CharacterParams.TargetParams targetParams, AIState state, float? priority = null)
|
||||
{
|
||||
if (targetParams == null) { return; }
|
||||
if (priority.HasValue)
|
||||
{
|
||||
targetParams.Priority = priority.Value;
|
||||
}
|
||||
targetParams.State = state;
|
||||
}
|
||||
|
||||
private void ResetParams(CharacterParams.TargetParams targetParams)
|
||||
{
|
||||
targetParams?.Reset();
|
||||
if (selectedTargetingParams == targetParams || State == AIState.Idle)
|
||||
{
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
PreviousState = AIState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
|
||||
{
|
||||
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
|
||||
@@ -2938,45 +3035,56 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPositionInsideAllowedZone(Vector2 pos, out Vector2 targetDir)
|
||||
{
|
||||
targetDir = Vector2.Zero;
|
||||
if (AIParams.AvoidAbyss)
|
||||
{
|
||||
if (pos.Y < Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far down
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
if (AIParams.StayInAbyss)
|
||||
{
|
||||
if (pos.Y > Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far up
|
||||
targetDir = -Vector2.UnitY;
|
||||
}
|
||||
else if (pos.Y < Level.Loaded.AbyssEnd)
|
||||
{
|
||||
// Too far down
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = 30000;
|
||||
if (pos.X < -margin)
|
||||
{
|
||||
// Too far left
|
||||
targetDir = Vector2.UnitX;
|
||||
}
|
||||
else if (pos.X > Level.Loaded.Size.X + margin)
|
||||
{
|
||||
// Too far right
|
||||
targetDir = -Vector2.UnitX;
|
||||
}
|
||||
return targetDir == Vector2.Zero;
|
||||
}
|
||||
|
||||
private Vector2 returnDir;
|
||||
private float returnTimer;
|
||||
private void SteerInsideLevel(float deltaTime)
|
||||
{
|
||||
if (State == AIState.Attack) { return; }
|
||||
if (SteeringManager is IndoorsSteeringManager) { return; }
|
||||
if (Level.Loaded == null) { return; }
|
||||
Point levelSize = Level.Loaded.Size;
|
||||
float returnTime = 10;
|
||||
if (AIParams.AvoidAbyss)
|
||||
if (State == AIState.Attack && returnTimer <= 0) { return; }
|
||||
float returnTime = 5;
|
||||
if (!IsPositionInsideAllowedZone(WorldPosition, out Vector2 targetDir))
|
||||
{
|
||||
if (WorldPosition.Y < Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far down
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
else if (AIParams.StayInAbyss)
|
||||
{
|
||||
if (WorldPosition.Y > Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far up
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = AIParams.AvoidAbyss ? 0 : 30000;
|
||||
if (WorldPosition.X < margin)
|
||||
{
|
||||
// Too far left
|
||||
returnDir = targetDir;
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitX;
|
||||
}
|
||||
if (WorldPosition.X > levelSize.X + margin)
|
||||
{
|
||||
// Too far right
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitX;
|
||||
}
|
||||
if (returnTimer > 0)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly AIObjectiveManager objectiveManager;
|
||||
|
||||
private float sortTimer;
|
||||
public float SortTimer { get; set; }
|
||||
private float crouchRaycastTimer;
|
||||
private float reactTimer;
|
||||
private float unreachableClearTimer;
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
outsideSteering = new SteeringManager(this);
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
reactTimer = GetReactionTime();
|
||||
sortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
SortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -218,6 +218,7 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != Character.Submarine) { continue; }
|
||||
if (c.Removed || c.IsDead || c.IsIncapacitated) { continue; }
|
||||
if (IsFriendly(c)) { continue; }
|
||||
Vector2 toTarget = c.WorldPosition - WorldPosition;
|
||||
float dist = toTarget.LengthSquared();
|
||||
@@ -264,14 +265,14 @@ namespace Barotrauma
|
||||
CheckCrouching(deltaTime);
|
||||
Character.ClearInputs();
|
||||
|
||||
if (sortTimer > 0.0f)
|
||||
if (SortTimer > 0.0f)
|
||||
{
|
||||
sortTimer -= deltaTime;
|
||||
SortTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.SortObjectives();
|
||||
sortTimer = sortObjectiveInterval;
|
||||
SortTimer = sortObjectiveInterval;
|
||||
}
|
||||
objectiveManager.UpdateObjectives(deltaTime);
|
||||
|
||||
@@ -288,14 +289,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (Character.IsOnPlayerTeam)
|
||||
{
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
}
|
||||
else
|
||||
{
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
}
|
||||
}
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
@@ -1065,7 +1066,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsFriendly(attacker))
|
||||
{
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
|
||||
return c.AIController is HumanAIController humanAI &&
|
||||
(humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders))
|
||||
? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1192,7 +1195,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Reset();
|
||||
objectiveManager.SortObjectives();
|
||||
sortTimer = sortObjectiveInterval;
|
||||
SortTimer = sortObjectiveInterval;
|
||||
float waitDuration = characterWaitOnSwitch;
|
||||
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
|
||||
{
|
||||
@@ -1418,6 +1421,9 @@ namespace Barotrauma
|
||||
item.StolenDuringRound = true;
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
|
||||
someoneSpoke = true;
|
||||
#if CLIENT
|
||||
HintManager.OnStoleItem(thief, item);
|
||||
#endif
|
||||
}
|
||||
// React if we are security
|
||||
if (!TriggerSecurity(otherHumanAI))
|
||||
@@ -1554,7 +1560,7 @@ namespace Barotrauma
|
||||
targetAdded = true;
|
||||
}
|
||||
}
|
||||
}, (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
|
||||
}, range: (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
|
||||
return targetAdded;
|
||||
}
|
||||
|
||||
@@ -1726,11 +1732,9 @@ namespace Barotrauma
|
||||
switch (myTeam)
|
||||
{
|
||||
case CharacterTeamType.None:
|
||||
// Only enemies are in the Team "None"
|
||||
return false;
|
||||
case CharacterTeamType.Team1:
|
||||
case CharacterTeamType.Team2:
|
||||
// Team1 is only friendly to Team1 and friendly NPCs
|
||||
// Only friendly to the same team and friendly NPCs
|
||||
return otherTeam == CharacterTeamType.FriendlyNPC;
|
||||
case CharacterTeamType.FriendlyNPC:
|
||||
// Friendly NPCs are friendly to both teams
|
||||
|
||||
@@ -221,6 +221,19 @@ namespace Barotrauma
|
||||
{
|
||||
currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType);
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode &&
|
||||
(campaignMode.Map?.CurrentLocation?.Type?.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase) ?? false))
|
||||
{
|
||||
if (speaker.TeamID == CharacterTeamType.None)
|
||||
{
|
||||
currentFlags.Add("Bandit");
|
||||
}
|
||||
else if (speaker.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
currentFlags.Add("Hostage");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentFlags;
|
||||
|
||||
+28
-16
@@ -79,6 +79,7 @@ namespace Barotrauma
|
||||
private float coolDownTimer;
|
||||
private IEnumerable<Body> myBodies;
|
||||
private float aimTimer;
|
||||
private float reloadTimer;
|
||||
private float spreadTimer;
|
||||
|
||||
private bool canSeeTarget;
|
||||
@@ -147,6 +148,7 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
spreadTimer = Rand.Range(-10, 10);
|
||||
HumanAIController.SortTimer = 0;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
@@ -170,6 +172,10 @@ namespace Barotrauma
|
||||
base.Update(deltaTime);
|
||||
ignoreWeaponTimer -= deltaTime;
|
||||
checkWeaponsTimer -= deltaTime;
|
||||
if (reloadTimer > 0)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
}
|
||||
if (ignoreWeaponTimer < 0)
|
||||
{
|
||||
ignoredWeapons.Clear();
|
||||
@@ -219,7 +225,11 @@ namespace Barotrauma
|
||||
{
|
||||
OperateWeapon(deltaTime);
|
||||
}
|
||||
if (!HoldPosition && seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
if (HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
{
|
||||
Move(deltaTime);
|
||||
}
|
||||
@@ -641,7 +651,7 @@ namespace Barotrauma
|
||||
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -912,7 +922,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 1f) / AimSpeed;
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
return;
|
||||
}
|
||||
if (Weapon.RequireAimToUse)
|
||||
@@ -930,6 +940,7 @@ namespace Barotrauma
|
||||
aimTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
@@ -1010,18 +1021,25 @@ namespace Barotrauma
|
||||
|
||||
private void UseWeapon(float deltaTime)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
float reloadTime = 0;
|
||||
if (WeaponComponent is RangedWeapon rangedWeapon)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
// If the weapon is just equipped, we can't shoot just yet.
|
||||
if (rangedWeapon.ReloadTimer <= 0)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
}
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
if (!((HumanoidAnimController)character.AnimController).Crouching)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
}
|
||||
}
|
||||
aimTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
@@ -1031,10 +1049,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
if (!HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -1044,10 +1059,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
if (!HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+4
-1
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
protected override float IgnoreListClearInterval => 30;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
protected override float TargetUpdateTimeMultiplier => 0.2f;
|
||||
|
||||
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
@@ -48,7 +50,8 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character)
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target == null || target.Removed) { return false; }
|
||||
if (target.IsDead || target.IsUnconscious) { return false; }
|
||||
if (target == character) { return false; }
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
|
||||
@@ -555,6 +555,13 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (yDiff > CloseEnough) { return false; }
|
||||
float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
|
||||
return xDiff <= CloseEnough;
|
||||
}
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
protected float targetUpdateTimer;
|
||||
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
|
||||
|
||||
private float syncTimer;
|
||||
private readonly float syncTime = 1;
|
||||
@@ -61,7 +62,7 @@ namespace Barotrauma
|
||||
ignoreListTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
if (targetUpdateTimer < 0)
|
||||
if (targetUpdateTimer <= 0)
|
||||
{
|
||||
UpdateTargets();
|
||||
}
|
||||
@@ -69,9 +70,9 @@ namespace Barotrauma
|
||||
{
|
||||
targetUpdateTimer -= deltaTime;
|
||||
}
|
||||
if (syncTimer < 0)
|
||||
if (syncTimer <= 0)
|
||||
{
|
||||
syncTimer = syncTime * Rand.Range(0.9f, 1.1f);
|
||||
syncTimer = Math.Min(syncTime * Rand.Range(0.9f, 1.1f), targetUpdateTimer);
|
||||
// Sync objectives, subobjectives and targets
|
||||
foreach (var objective in Objectives)
|
||||
{
|
||||
@@ -95,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
||||
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1);
|
||||
private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
@@ -156,7 +157,7 @@ namespace Barotrauma
|
||||
|
||||
protected void UpdateTargets()
|
||||
{
|
||||
SetTargetUpdateTimer();
|
||||
CalculateTargetUpdateTimer();
|
||||
Targets.Clear();
|
||||
FindTargets();
|
||||
CreateObjectives();
|
||||
|
||||
+3
-1
@@ -386,7 +386,9 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
bool isCompleted =
|
||||
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
|
||||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
|
||||
|
||||
+3
-3
@@ -25,8 +25,8 @@ namespace Barotrauma
|
||||
{
|
||||
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
|
||||
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not nevessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public const float MAX_SPEED = 30;
|
||||
public const float MAX_SPEED = 20;
|
||||
|
||||
public Vector2 TargetMovement
|
||||
{
|
||||
@@ -636,9 +636,12 @@ namespace Barotrauma
|
||||
//always collides with bodies other than structures
|
||||
if (!(f2.Body.UserData is Structure structure))
|
||||
{
|
||||
lock (impactQueue)
|
||||
if (!f2.IsSensor)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
lock (impactQueue)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,8 @@ namespace Barotrauma
|
||||
|
||||
public Entity LastDamageSource;
|
||||
|
||||
public AttackResult LastDamage;
|
||||
|
||||
public float InvisibleTimer;
|
||||
|
||||
private CharacterPrefab prefab;
|
||||
@@ -199,7 +201,12 @@ namespace Barotrauma
|
||||
set => Params.Visibility = value;
|
||||
}
|
||||
|
||||
public bool IsTraitor;
|
||||
public bool IsTraitor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string TraitorCurrentObjective = "";
|
||||
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
|
||||
public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male;
|
||||
@@ -333,6 +340,7 @@ namespace Barotrauma
|
||||
//text displayed when the character is highlighted if custom interact is set
|
||||
public string customInteractHUDText;
|
||||
private Action<Character, Character> onCustomInteract;
|
||||
public ConversationAction ActiveConversation;
|
||||
|
||||
public bool AllowCustomInteract
|
||||
{
|
||||
@@ -349,6 +357,9 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
|
||||
#if CLIENT
|
||||
HintManager.OnHandcuffed(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,6 +616,9 @@ namespace Barotrauma
|
||||
get => _selectedConstruction;
|
||||
set
|
||||
{
|
||||
#if CLIENT
|
||||
HintManager.OnSetSelectedConstruction(this, _selectedConstruction, value);
|
||||
#endif
|
||||
_selectedConstruction = value;
|
||||
#if CLIENT
|
||||
if (Controlled == this)
|
||||
@@ -1661,6 +1675,12 @@ namespace Barotrauma
|
||||
{
|
||||
item.Use(deltaTime, this);
|
||||
}
|
||||
#if CLIENT
|
||||
else if (item.RequireAimToUse && !IsKeyDown(InputType.Aim))
|
||||
{
|
||||
HintManager.OnShootWithoutAiming(this, item);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1853,6 +1873,19 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public Item GetEquippedItem(string tagOrIdentifier)
|
||||
{
|
||||
if (Inventory == null) { return null; }
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
{
|
||||
if (Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
|
||||
var item = Inventory.GetItemAt(i);
|
||||
if (item == null) { continue; }
|
||||
if (item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool CanAccessInventory(Inventory inventory)
|
||||
{
|
||||
if (!CanInteract || inventory.Locked) { return false; }
|
||||
@@ -2857,6 +2890,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (character == this) { continue; }
|
||||
if (character.TeamID != TeamID) { continue; }
|
||||
if (!HumanAIController.IsActive(character)) { continue; }
|
||||
foreach (var currentOrder in character.CurrentOrders)
|
||||
{
|
||||
if (currentOrder.Order == null) { continue; }
|
||||
@@ -3268,12 +3302,13 @@ namespace Barotrauma
|
||||
//#endif
|
||||
// }
|
||||
|
||||
SetStun(stun);
|
||||
|
||||
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
|
||||
{
|
||||
if (attacker.TeamID == TeamID) { return new AttackResult(); }
|
||||
}
|
||||
|
||||
SetStun(stun);
|
||||
Vector2 dir = hitLimb.WorldPosition - worldPosition;
|
||||
if (Math.Abs(attackImpulse) > 0.0f)
|
||||
{
|
||||
@@ -3308,6 +3343,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (attackResult.Damage > 0)
|
||||
{
|
||||
LastDamage = attackResult;
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
if (attacker != null)
|
||||
|
||||
@@ -153,6 +153,8 @@ namespace Barotrauma
|
||||
private static ushort idCounter;
|
||||
private const string disguiseName = "???";
|
||||
|
||||
public bool HasNickname => Name != OriginalName;
|
||||
public string OriginalName { get; private set; }
|
||||
public string Name;
|
||||
public string DisplayName
|
||||
{
|
||||
@@ -453,7 +455,7 @@ namespace Barotrauma
|
||||
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
|
||||
|
||||
// Used for creating the data
|
||||
public CharacterInfo(string speciesName, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -503,6 +505,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
|
||||
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
|
||||
Salary = CalculateSalary();
|
||||
if (ragdollFileName != null)
|
||||
@@ -518,6 +521,7 @@ namespace Barotrauma
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
Name = infoElement.GetAttributeString("name", "");
|
||||
OriginalName = infoElement.GetAttributeString("originalname", null);
|
||||
string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
Salary = infoElement.GetAttributeInt("salary", 1000);
|
||||
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race);
|
||||
@@ -576,6 +580,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(OriginalName))
|
||||
{
|
||||
OriginalName = Name;
|
||||
}
|
||||
|
||||
StartItemsGiven = infoElement.GetAttributeBool("startitemsgiven", false);
|
||||
string personalityName = infoElement.GetAttributeString("personality", "");
|
||||
ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty);
|
||||
@@ -622,7 +631,17 @@ namespace Barotrauma
|
||||
|
||||
public int GetIdentifier()
|
||||
{
|
||||
int id = ToolBox.StringToInt(Name);
|
||||
return GetIdentifier(Name);
|
||||
}
|
||||
|
||||
public int GetIdentifierUsingOriginalName()
|
||||
{
|
||||
return GetIdentifier(OriginalName);
|
||||
}
|
||||
|
||||
private int GetIdentifier(string name)
|
||||
{
|
||||
int id = ToolBox.StringToInt(name);
|
||||
id ^= HeadSpriteId;
|
||||
id ^= (int)Race << 6;
|
||||
id ^= HairIndex << 12;
|
||||
@@ -939,12 +958,24 @@ namespace Barotrauma
|
||||
|
||||
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos);
|
||||
|
||||
public void Rename(string newName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newName)) { return; }
|
||||
Name = newName;
|
||||
}
|
||||
|
||||
public void ResetName()
|
||||
{
|
||||
Name = OriginalName;
|
||||
}
|
||||
|
||||
public XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement charElement = new XElement("Character");
|
||||
|
||||
charElement.Add(
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("originalname", OriginalName),
|
||||
new XAttribute("speciesname", SpeciesName),
|
||||
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"),
|
||||
new XAttribute("race", Head.race.ToString()),
|
||||
@@ -957,7 +988,7 @@ namespace Barotrauma
|
||||
new XAttribute("startitemsgiven", StartItemsGiven),
|
||||
new XAttribute("ragdoll", ragdollFileName),
|
||||
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
|
||||
|
||||
|
||||
// TODO: animations?
|
||||
|
||||
if (Character != null)
|
||||
|
||||
+5
-2
@@ -260,10 +260,13 @@ namespace Barotrauma
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to skip clamping and additional logic of the setters.
|
||||
/// Intended only to be used when the value is already clamped! (networking code)
|
||||
/// Ideally we would keep this private, but doing so would require too much refactoring.
|
||||
/// </summary>
|
||||
public void SetStrength(float strength) => _strength = strength;
|
||||
public void SetStrength(float strength)
|
||||
{
|
||||
_nonClampedStrength = strength;
|
||||
_strength = _nonClampedStrength;
|
||||
}
|
||||
|
||||
public bool ShouldShowIcon(Character afflictedCharacter)
|
||||
{
|
||||
|
||||
+28
-3
@@ -290,6 +290,9 @@ namespace Barotrauma
|
||||
//how high the strength has to be for the affliction icon to be shown with a health scanner
|
||||
public readonly float ShowInHealthScannerThreshold = 0.05f;
|
||||
|
||||
//how strong the affliction needs to be before bots attempt to treat it
|
||||
public readonly float TreatmentThreshold = 5.0f;
|
||||
|
||||
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
|
||||
public float KarmaChangeOnApplied;
|
||||
|
||||
@@ -376,6 +379,9 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot override all afflictions, because many of them are required by the main game! Please try overriding them one by one.");
|
||||
}
|
||||
|
||||
List<(AfflictionPrefab prefab, XElement element)> loadedAfflictions = new List<(AfflictionPrefab prefab, XElement element)>();
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
@@ -510,10 +516,18 @@ namespace Barotrauma
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
loadedAfflictions.Add((prefab, element));
|
||||
Prefabs.Add(prefab, isOverride);
|
||||
prefab.CalculatePrefabUIntIdentifier(Prefabs);
|
||||
}
|
||||
}
|
||||
|
||||
//load the effects after all the afflictions in the file have been instantiated
|
||||
//otherwise afflictions can't inflict other afflictions that are defined at a later point in the file
|
||||
foreach ((AfflictionPrefab prefab, XElement element) in loadedAfflictions)
|
||||
{
|
||||
prefab.LoadEffects(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveByFile(string filePath)
|
||||
@@ -565,6 +579,7 @@ namespace Barotrauma
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
|
||||
@@ -584,9 +599,6 @@ namespace Barotrauma
|
||||
case "icon":
|
||||
Icon = new Sprite(subElement);
|
||||
break;
|
||||
case "effect":
|
||||
effects.Add(new Effect(subElement, Name));
|
||||
break;
|
||||
case "periodiceffect":
|
||||
periodicEffects.Add(new PeriodicEffect(subElement, Name));
|
||||
break;
|
||||
@@ -614,6 +626,19 @@ namespace Barotrauma
|
||||
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
|
||||
}
|
||||
|
||||
private void LoadEffects(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "effect":
|
||||
effects.Add(new Effect(subElement, Name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "AfflictionPrefab (" + Name + ")";
|
||||
|
||||
@@ -36,7 +36,11 @@ namespace Barotrauma
|
||||
|
||||
public LimbHealth(XElement element, CharacterHealth characterHealth)
|
||||
{
|
||||
Name = TextManager.Get("HealthLimbName." + element.GetAttributeString("name", ""));
|
||||
string limbName = element.GetAttributeString("name", null) ?? "generic";
|
||||
if (limbName != "generic")
|
||||
{
|
||||
Name = TextManager.Get("HealthLimbName." + limbName);
|
||||
}
|
||||
this.characterHealth = characterHealth;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -664,7 +668,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
partial void UpdateLimbAfflictionOverlays();
|
||||
@@ -687,6 +690,10 @@ namespace Barotrauma
|
||||
{
|
||||
var affliction = limbHealths[i].Afflictions[j];
|
||||
Limb targetLimb = Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == i);
|
||||
if (targetLimb == null)
|
||||
{
|
||||
targetLimb = Character.AnimController.MainLimb;
|
||||
}
|
||||
affliction.Update(this, targetLimb, deltaTime);
|
||||
affliction.DamagePerSecondTimer += deltaTime;
|
||||
if (affliction is AfflictionBleeding bleeding)
|
||||
@@ -877,6 +884,7 @@ namespace Barotrauma
|
||||
float minSuitability = -10, maxSuitability = 10;
|
||||
foreach (Affliction affliction in GetAllAfflictions())
|
||||
{
|
||||
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
if (!treatmentSuitability.ContainsKey(treatment.Key))
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace Barotrauma
|
||||
[Serialize(1f, false)]
|
||||
public float HealthMultiplier { get; protected set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float HealthMultiplierInMultiplayer { get; protected set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float AimSpeed { get; protected set; }
|
||||
|
||||
@@ -117,6 +120,10 @@ namespace Barotrauma
|
||||
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= HealthMultiplier;
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= HealthMultiplierInMultiplayer;
|
||||
}
|
||||
var humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
|
||||
@@ -732,6 +732,10 @@ namespace Barotrauma
|
||||
{
|
||||
newAffliction = affliction.CreateMultiplied(finalDamageModifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
newAffliction.SetStrength(affliction.NonClampedStrength);
|
||||
}
|
||||
|
||||
if (applyAffliction)
|
||||
{
|
||||
|
||||
@@ -430,10 +430,10 @@ namespace Barotrauma
|
||||
[Serialize(false, true)]
|
||||
public bool UseHealthWindow { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
[Serialize(0f, true, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
public float BleedingReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
[Serialize(0f, true, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
public float BurnReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
|
||||
Reference in New Issue
Block a user