Unstable 0.16.2.0

This commit is contained in:
Markus Isberg
2022-02-02 00:55:02 +09:00
parent b259af5911
commit 6814a11520
106 changed files with 1634 additions and 793 deletions
@@ -531,8 +531,7 @@ namespace Barotrauma
selectedTargetingParams = targetingParams;
State = targetingParams.State;
}
if (SelectedAiTarget?.Entity != null &&
(LatchOntoAI == null || !LatchOntoAI.IsAttached || wallTarget != null) &&
if ((LatchOntoAI == null || !LatchOntoAI.IsAttached || wallTarget != null) &&
(State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive))
{
UpdateWallTarget(requiredHoleCount);
@@ -646,7 +645,7 @@ namespace Barotrauma
}
else
{
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
run = isBeingChased || squaredDistance < Math.Pow(halfReactDistance, 2);
State = AIState.Escape;
avoidTimer = AIParams.AvoidTime * 0.5f * Rand.Range(0.75f, 1.25f);
}
@@ -674,7 +673,8 @@ namespace Barotrauma
Character c = a.Character;
if (c.IsDead || c.Removed) { return false; }
if (!Character.IsFriendly(c)) { return true; }
// Only apply the threshold to friendly characters
if (!c.IsPlayer) { return false; }
// Only apply the threshold to players
return a.Damage >= selectedTargetingParams.Threshold;
}
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
@@ -686,6 +686,8 @@ namespace Barotrauma
// Attack the character that attacked the target we are protecting
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
SelectTarget(attacker.AiTarget);
State = AIState.Attack;
UpdateWallTarget(requiredHoleCount);
return;
}
}
@@ -2270,6 +2272,10 @@ namespace Barotrauma
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
{
State = AIState.Idle;
if (Character.SelectedCharacter != null)
{
Character.DeselectCharacter();
}
return;
}
if (SelectedAiTarget.Entity is Character || SelectedAiTarget.Entity is Item)
@@ -2285,7 +2291,16 @@ namespace Barotrauma
Vector2 attackSimPosition = Character.GetRelativeSimPosition(SelectedAiTarget.Entity);
Vector2 limbDiff = attackSimPosition - mouthPos;
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2);
if (limbDiff.LengthSquared() < extent * extent)
bool tooFar = Character.InWater ? limbDiff.LengthSquared() > extent * extent : limbDiff.X > extent;
if (tooFar)
{
steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), 2);
if (Character.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
else
{
if (SelectedAiTarget.Entity is Character targetCharacter)
{
@@ -2301,11 +2316,9 @@ namespace Barotrauma
{
item.body.LinearVelocity *= 0.9f;
item.body.LinearVelocity -= limbDiff * 0.25f;
bool wasBroken = item.Condition <= 0.0f;
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime);
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
if (item.Condition <= 0.0f)
{
if (!wasBroken) { PetBehavior?.OnEat(item); }
@@ -2317,14 +2330,6 @@ namespace Barotrauma
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(limbDiff) * 3);
Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos);
}
else
{
steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), 2);
if (Character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
}
else
{
@@ -2392,7 +2397,7 @@ namespace Barotrauma
selectedTargetMemory = null;
targetingParams = null;
bool isAnyTargetClose = false;
bool isBeingChased = IsBeingChased;
foreach (AITarget aiTarget in AITarget.List)
{
if (aiTarget.InDetectable) { continue; }
@@ -2515,11 +2520,12 @@ namespace Barotrauma
// Ignore inner walls when outside (walltargets still work)
continue;
}
valueModifier = 1;
if (!Character.AnimController.CanEnterSubmarine && IsWallDisabled(s))
{
continue;
}
// Prefer weaker walls (200 is the default for normal hull walls)
valueModifier = 200f / s.MaxHealth;
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
@@ -2674,6 +2680,10 @@ namespace Barotrauma
}
}
}
if (targetParams.State == AIState.Eat && Character.Params.Health.HealthRegenerationWhenEating > 0)
{
valueModifier *= MathHelper.Lerp(1f, 0.1f, Character.HealthPercentage / 100f);
}
valueModifier *= targetParams.Priority;
if (valueModifier == 0.0f) { continue; }
if (targetingTag != "decoy")
@@ -2720,9 +2730,21 @@ namespace Barotrauma
// Stick to the current target
valueModifier *= 1.1f;
}
if (!isBeingChased)
{
if (targetParams.State == AIState.Avoid || targetParams.State == AIState.PassiveAggressive || targetParams.State == AIState.Aggressive)
{
float reactDistance = targetParams.ReactDistance;
if (reactDistance > 0 && reactDistance < dist)
{
// The target is too far and should be ignored.
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
// -> just ignore the distance and target whatever has the highest priority
dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null)
@@ -2801,9 +2823,9 @@ namespace Barotrauma
{
if (Character.CurrentHull != null && targetCharacter.CurrentHull != Character.CurrentHull)
{
if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe)
if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe || targetParams.State == AIState.Eat)
{
// Ignore targets that cannot see
// Ignore targets that cannot be seen
if (!VisibleHulls.Contains(targetCharacter.CurrentHull))
{
continue;
@@ -3295,7 +3317,7 @@ namespace Barotrauma
{
if (priority.HasValue)
{
targetParams.Priority = priority.Value;
targetParams.Priority = Math.Max(targetParams.Priority, priority.Value);
}
targetParams.State = state;
if (!modifiedParams.ContainsKey(tag))
@@ -3314,6 +3336,7 @@ namespace Barotrauma
/// <summary>
/// Temporarily changes the predefined state for a target. Eg. Idle -> Attack.
/// Note: does not change the current AIState!
/// </summary>
private void ChangeTargetState(Character target, AIState state, float? priority = null)
{
@@ -3335,14 +3358,14 @@ namespace Barotrauma
// --> Target the submarine too.
if (target.Submarine != null && Character.Submarine == null && (canAttackDoors || canAttackWalls))
{
ChangeParams("room", state, priority * 0.1f);
ChangeParams("room", state, priority / 2);
if (canAttackWalls)
{
ChangeParams("wall", state, priority * 0.1f);
ChangeParams("wall", state, priority / 2);
}
if (canAttackDoors)
{
ChangeParams("door", state, priority * 0.1f);
ChangeParams("door", state, priority / 2);
}
}
ChangeParams("provocative", state, priority, onlyExisting: true);
@@ -3394,9 +3417,15 @@ namespace Barotrauma
private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1, bool checkVisibility = false)
{
if (target?.Entity == null) { return false; }
bool insideSightRange;
bool insideSoundRange;
checkVisibility = checkVisibility && Character.Submarine != null && target.Entity.Submarine == Character.Submarine;
if (checkVisibility)
{
// We only want to check the visibility when the target is in ruins/wreck/similiar place where sneaking should be possible.
// When the monsters attack the player sub, they wall hack so that they can be more aggressive.
checkVisibility = target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
}
if (dist > 0)
{
insideSightRange = IsInRange(dist, target.SightRange, Sight);
@@ -565,7 +565,7 @@ namespace Barotrauma
Character.AnimController.HeadInWater ||
Character.Submarine == null ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
!ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>() && ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOn) ||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOnAlsoWhenInactive) ||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10;
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
@@ -878,7 +878,7 @@ namespace Barotrauma
foreach (Character target in Character.CharacterList)
{
if (target.CurrentHull != hull || !target.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
if (AIObjectiveFightIntruders.IsValidTarget(target, Character, false))
{
if (!target.IsArrested && AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
{
@@ -1772,7 +1772,7 @@ namespace Barotrauma
foreach (var enemy in Character.CharacterList)
{
if (enemy.CurrentHull != hull) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(enemy, character))
if (AIObjectiveFightIntruders.IsValidTarget(enemy, character, false))
{
AddTargets<AIObjectiveFightIntruders, Character>(character, enemy);
}
@@ -9,10 +9,10 @@ namespace Barotrauma
{
class IndoorsSteeringManager : SteeringManager
{
private PathFinder pathFinder;
private readonly PathFinder pathFinder;
private SteeringPath currentPath;
private bool canOpenDoors;
private readonly bool canOpenDoors;
public bool CanBreakDoors { get; set; }
private bool ShouldBreakDoor(Door door) =>
@@ -20,7 +20,7 @@ namespace Barotrauma
!door.Item.Indestructible && !door.Item.InvulnerableToDamage &&
(door.Item.Submarine == null || door.Item.Submarine.TeamID != character.TeamID);
private Character character;
private readonly Character character;
private Vector2 currentTarget;
@@ -77,8 +77,10 @@ namespace Barotrauma
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) : base(host)
{
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
pathFinder.GetNodePenalty = GetNodePenalty;
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true)
{
GetNodePenalty = GetNodePenalty
};
this.canOpenDoors = canOpenDoors;
this.CanBreakDoors = canBreakDoors;
@@ -508,6 +510,16 @@ namespace Barotrauma
canAccessButtons = true;
}
}
foreach (var linked in door.Item.linkedTo)
{
if (!(linked is Item linkedItem)) { continue; }
var button = linkedItem.GetComponent<Controller>();
if (button == null) { continue; }
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
{
canAccessButtons = true;
}
}
return canAccessButtons || door.IsOpen || ShouldBreakDoor(door);
}
}
@@ -30,6 +30,8 @@ namespace Barotrauma
public virtual bool ConcurrentObjectives => false;
public virtual bool KeepDivingGearOn => false;
public virtual bool KeepDivingGearOnAlsoWhenInactive => false;
/// <summary>
/// There's a separate property for diving suit and mask: KeepDivingGearOn.
/// </summary>
@@ -12,10 +12,12 @@ namespace Barotrauma
protected override float TargetUpdateTimeMultiplier => 0.2f;
public bool TargetCharactersInOtherSubs { get; set; }
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
protected override bool Filter(Character target) => IsValidTarget(target, character);
protected override bool Filter(Character target) => IsValidTarget(target, character, TargetCharactersInOtherSubs);
protected override IEnumerable<Character> GetList() => Character.CharacterList;
@@ -54,7 +56,7 @@ namespace Barotrauma
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
=> HumanAIController.RemoveTargets<AIObjectiveFightIntruders, Character>(character, target);
public static bool IsValidTarget(Character target, Character character)
public static bool IsValidTarget(Character target, Character character, bool targetCharactersInOtherSubs)
{
if (target == null || target.Removed) { return false; }
if (target.IsDead) { return false; }
@@ -65,7 +67,7 @@ namespace Barotrauma
if (target.CurrentHull == null) { return false; }
if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (target.IsArrested) { return false; }
return true;
@@ -629,18 +629,20 @@ namespace Barotrauma
{
get
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
if (character.IsClimbing)
{
// Climbing a ladder
if (Target.WorldPosition.Y > character.WorldPosition.Y)
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
{
// The target is still above us
return false;
}
if (!character.AnimController.IsAboveFloor)
{
// Going through a hatch
return false;
if (Target.WorldPosition.Y > character.WorldPosition.Y)
{
// The target is still above us
return false;
}
if (!character.AnimController.IsAboveFloor)
{
// Going through a hatch
return false;
}
}
}
if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
@@ -422,7 +422,7 @@ namespace Barotrauma
case "wait":
newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
{
AllowGoingOutside = character.Submarine == null || (order.TargetSpatialEntity != null && character.Submarine != order.TargetSpatialEntity.Submarine)
AllowGoingOutside = true
};
break;
case "return":
@@ -468,6 +468,12 @@ namespace Barotrauma
case "fightintruders":
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
break;
case "assaultenemy":
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier)
{
TargetCharactersInOtherSubs = true
};
break;
case "steer":
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "prepare";
public override string DebugTag => $"{Identifier}";
public override bool KeepDivingGearOn => true;
public override bool KeepDivingGearOnAlsoWhenInactive => true;
public override bool PrioritizeIfSubObjectivesActive => true;
private AIObjectiveGetItem getSingleItemObjective;
@@ -155,6 +155,9 @@ namespace Barotrauma
public OrderCategory? Category { get; private set; }
//legacy support
/// <summary>
/// If defined, the order can only be quick-assigned to characters with these jobs. Or if it's a report, the icon will only be displayed to characters with these jobs.
/// </summary>
public readonly string[] AppropriateJobs;
public readonly string[] Options;
public readonly string[] HiddenOptions;
@@ -177,6 +180,10 @@ namespace Barotrauma
public bool IsPrefab { get; private set; }
public readonly bool MustManuallyAssign;
public readonly bool AutoDismiss;
/// <summary>
/// If defined, the order will be quick-assigned to characters with these jobs before characters with other jobs.
/// </summary>
public string[] PreferredJobs { get; }
public readonly OrderTarget TargetPosition;
@@ -327,6 +334,7 @@ namespace Barotrauma
ControllerTags = orderElement.GetAttributeStringArray("controllertags", new string[0]);
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
PreferredJobs = orderElement.GetAttributeStringArray("preferredjobs", new string[0]);
Options = orderElement.GetAttributeStringArray("options", new string[0]);
HiddenOptions = orderElement.GetAttributeStringArray("hiddenoptions", new string[0]);
AllOptions = Options.Concat(HiddenOptions).ToArray();
@@ -407,7 +415,7 @@ namespace Barotrauma
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
IsIgnoreOrder = Identifier == "ignorethis" || Identifier == "unignorethis";
DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Operate || Category == OrderCategory.Movement);
AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100);
ColoredWhenControllingGiver = orderElement.GetAttributeBool("coloredwhencontrollinggiver", false);
DisplayGiverInTooltip = orderElement.GetAttributeBool("displaygiverintooltip", false);
@@ -435,6 +443,7 @@ namespace Barotrauma
ControllerTags = prefab.ControllerTags;
TargetAllCharacters = prefab.TargetAllCharacters;
AppropriateJobs = prefab.AppropriateJobs;
PreferredJobs = prefab.PreferredJobs;
FadeOutTime = prefab.FadeOutTime;
MustSetTarget = prefab.MustSetTarget;
CanBeGeneralized = prefab.CanBeGeneralized;
@@ -446,8 +455,9 @@ namespace Barotrauma
Hidden = prefab.Hidden;
IgnoreAtOutpost = prefab.IgnoreAtOutpost;
AssignmentPriority = prefab.AssignmentPriority;
ColoredWhenControllingGiver = prefab.ColoredWhenControllingGiver;
AutoDismiss = prefab.AutoDismiss;
DisplayGiverInTooltip = prefab.DisplayGiverInTooltip;
ColoredWhenControllingGiver = prefab.ColoredWhenControllingGiver;
OrderGiver = orderGiver;
TargetEntity = targetEntity;
@@ -488,30 +498,37 @@ namespace Barotrauma
WallSectionIndex = sectionIndex;
TargetType = OrderTargetType.WallSection;
}
public bool HasAppropriateJob(Character character)
{
if (character.Info == null || character.Info.Job == null) { return false; }
if (character.Info.Job.Prefab.AppropriateOrders.Any(appropriateOrderId => Identifier == appropriateOrderId)) { return true; }
if (!JobPrefab.Prefabs.Any(jp => jp.AppropriateOrders.Contains(Identifier)) &&
(AppropriateJobs == null || AppropriateJobs.Length == 0))
private bool HasSpecifiedJob(Character character, string[] jobs)
{
if (jobs == null || jobs.Length == 0) { return false; }
string jobIdentifier = character?.Info?.Job?.Prefab?.Identifier;
if (string.IsNullOrEmpty(jobIdentifier)) { return false; }
for (int i = 0; i < jobs.Length; i++)
{
return true;
}
for (int i = 0; i < AppropriateJobs.Length; i++)
{
if (character.Info.Job.Prefab.Identifier.Equals(AppropriateJobs[i], StringComparison.OrdinalIgnoreCase)) { return true; }
if (jobIdentifier.Equals(jobs[i], StringComparison.OrdinalIgnoreCase)) { return true; }
}
return false;
}
public bool HasAppropriateJob(Character character) => HasSpecifiedJob(character, AppropriateJobs);
public bool HasPreferredJob(Character character) => HasSpecifiedJob(character, PreferredJobs);
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", bool isNewOrder = true)
{
if (!TargetAllCharacters && !isNewOrder && Identifier != "dismissed")
{
// Use special dialogue when we're rearranging character orders
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
if (!givingOrderToSelf)
{
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
}
else
{
// Say nothing when rearranging the orders of the character you're controlling
return string.Empty;
}
}
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}.{Identifier}";
if (!string.IsNullOrEmpty(orderOption))
@@ -346,7 +346,12 @@ namespace Barotrauma
Vector2 limbDiff = attackSimPosition - mouthPos;
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 1);
if (limbDiff.LengthSquared() < extent * extent)
bool tooFar = character.InWater ? limbDiff.LengthSquared() > extent * extent : limbDiff.X > extent;
if (tooFar)
{
character.SelectedCharacter = null;
}
else
{
//pull the target character to the position of the mouth
//(+ make the force fluctuate to waggle the character a bit)
@@ -383,7 +388,7 @@ namespace Barotrauma
mouthLimb.body.ApplyTorque(-force * 50);
}
if (Character.CanEat)
if (Character.CanEat && target.IsDead)
{
var jaw = GetLimb(LimbType.Jaw);
if (jaw != null)
@@ -432,10 +437,6 @@ namespace Barotrauma
}
}
}
else
{
character.SelectedCharacter = null;
}
}
public bool reverse;
@@ -533,7 +533,7 @@ namespace Barotrauma
bool onSlope = Math.Abs(movement.X) > 0.01f && Math.Abs(floorNormal.X) > 0.1f && Math.Sign(floorNormal.X) != Math.Sign(movement.X);
bool movingHorizontally = !MathUtils.NearlyEqual(targetMovement.X, 0.0f);
bool movingHorizontally = !MathUtils.NearlyEqual(TargetMovement.X, 0.0f);
if (Stairs != null || onSlope)
{
@@ -304,7 +304,27 @@ namespace Barotrauma
public abstract float? TorsoPosition { get; }
public abstract float? TorsoAngle { get; }
public float ImpactTolerance => RagdollParams.ImpactTolerance;
float? impactTolerance;
public float ImpactTolerance
{
get
{
if (impactTolerance == null)
{
impactTolerance = RagdollParams.ImpactTolerance;
if (character.Params.VariantFile != null)
{
float? tolerance = character.Params.VariantFile.Root.GetChildElement("ragdoll")?.GetAttributeFloat("impacttolerance", impactTolerance.Value);
if (tolerance.HasValue)
{
impactTolerance = tolerance;
}
}
}
return impactTolerance.Value;
}
}
public bool Draggable => RagdollParams.Draggable;
public bool CanEnterSubmarine => RagdollParams.CanEnterSubmarine;
@@ -1833,7 +1853,7 @@ namespace Barotrauma
float sin = (float)Math.Sin(mouthLimb.Rotation);
Vector2 bodySize = mouthLimb.body.GetSize();
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * mouthLimb.Scale * RagdollParams.LimbScale;
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos);
}
public Vector2 GetColliderBottom()
@@ -101,11 +101,21 @@ namespace Barotrauma
[Serialize(false, true, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
public bool Retreat { get; private set; }
private float _range;
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
public float Range { get; set; }
public float Range
{
get => _range * RangeMultiplier;
set => _range = value;
}
private float _damageRange;
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target to do damage. In distance-based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
public float DamageRange { get; set; }
public float DamageRange
{
get => _damageRange * RangeMultiplier;
set => _damageRange = value;
}
[Serialize(0.25f, true, description: "An approximation of the attack duration. Effectively defines the time window in which the hit can be registered. If set to too low value, it's possible that the attack won't hit the target in time."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, DecimalCount = 2)]
public float Duration { get; private set; }
@@ -145,10 +155,20 @@ namespace Barotrauma
public float Penetration { get; private set; }
/// <summary>
/// Currently only used with variants. Used for multiplying all the damage.
/// Used for multiplying all the damage.
/// </summary>
public float DamageMultiplier { get; set; } = 1;
/// <summary>
/// Used for multiplying all the ranges.
/// </summary>
public float RangeMultiplier { get; set; } = 1;
/// <summary>
/// Used for multiplying the physics forces.
/// </summary>
public float ImpactMultiplier { get; set; } = 1;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float LevelWallDamage { get; set; }
@@ -1249,6 +1249,10 @@ namespace Barotrauma
Info.HairElement?.Elements("sprite").ForEach(s => head.OtherWearables.Add(new WearableSprite(s, WearableType.Hair)));
#if CLIENT
if (info.Head?.HairWithHatElement != null)
{
head.HairWithHatSprite = new WearableSprite(info.Head?.HairWithHatElement.Element("sprite"), WearableType.Hair);
}
head.EnableHuskSprite = Params.Husk;
head.LoadHerpesSprite();
head.UpdateWearableTypesToHide();
@@ -3499,7 +3503,7 @@ namespace Barotrauma
Limb limbHit = targetLimb;
float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime;
float attackImpulse = attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier * deltaTime;
AbilityAttackData attackData = new AbilityAttackData(attack, this);
if (attacker != null)
@@ -3537,7 +3541,7 @@ namespace Barotrauma
}
if (limbHit == null) { return new AttackResult(); }
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld * attack.ImpactMultiplier;
if (attacker != null)
{
forceWorld.X *= attacker.AnimController.Dir;
@@ -3845,40 +3849,49 @@ namespace Barotrauma
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
statusEffect.Apply(actionType, deltaTime, this, targets);
}
else
else if (statusEffect.targetLimbs != null)
{
statusEffect.Apply(actionType, deltaTime, this, this);
if (statusEffect.targetLimbs != null)
foreach (var limbType in statusEffect.targetLimbs)
{
foreach (var limbType in statusEffect.targetLimbs)
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
// Target all matching limbs
foreach (var limb in AnimController.Limbs)
{
// Target all matching limbs
foreach (var limb in AnimController.Limbs)
if (limb.IsSevered) { continue; }
if (limb.type == limbType)
{
if (limb.IsSevered) { continue; }
if (limb.type == limbType)
{
statusEffect.Apply(actionType, deltaTime, this, limb);
}
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
{
// Target just the first matching limb
Limb limb = AnimController.GetLimb(limbType);
if (limb != null)
{
// Target just the first matching limb
Limb limb = AnimController.GetLimb(limbType);
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
{
// Target just the last matching limb
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
if (limb != null)
{
// Target just the last matching limb
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
{
statusEffect.Apply(actionType, deltaTime, this, this);
}
}
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
{
@@ -43,6 +43,7 @@ namespace Barotrauma
public int FaceAttachmentIndex { get; set; } = -1;
public XElement HairElement { get; set; }
public XElement HairWithHatElement { get; set; }
public XElement BeardElement { get; set; }
public XElement MoustacheElement { get; set; }
public XElement FaceAttachment { get; set; }
@@ -1125,6 +1126,16 @@ namespace Barotrauma
Head.HairElement = GetRandomElement(hairs);
Head.HairIndex = hairs.IndexOf(Head.HairElement);
}
if (Head.HairElement != null)
{
int thisHairIndex = hairs.IndexOf(head.HairElement);
int hairWithHatIndex = head.HairElement.GetAttributeInt("replacewhenwearinghat", thisHairIndex);
if (thisHairIndex != hairWithHatIndex && hairWithHatIndex > -1 && hairWithHatIndex < hairs.Count)
{
head.HairWithHatElement = hairs[hairWithHatIndex];
}
}
if (IsValidIndex(Head.BeardIndex, beards))
{
Head.BeardElement = beards[Head.BeardIndex];
@@ -649,6 +649,8 @@ namespace Barotrauma
if (attackElement != null)
{
attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f);
attack.RangeMultiplier = attackElement.GetAttributeFloat("rangemultiplier", 1f);
attack.ImpactMultiplier = attackElement.GetAttributeFloat("impactmultiplier", 1f);
}
}
break;
@@ -14,9 +14,9 @@ namespace Barotrauma
NotDefined = 0,
Walk = 1,
Run = 2,
Crouch = 3,
SwimSlow = 4,
SwimFast = 5
SwimSlow = 3,
SwimFast = 4,
Crouch = 5
}
abstract class GroundedMovementParams : AnimationParams
@@ -42,7 +42,7 @@ namespace Barotrauma
}
private float skillIncreasePerRepairedStructureDamage;
[Serialize(0.005f, true)]
[Serialize(0.0025f, true)]
public float SkillIncreasePerRepairedStructureDamage
{
get { return skillIncreasePerRepairedStructureDamage * GetCurrentSkillGainMultiplier(); }