Unstable 0.16.0.0
This commit is contained in:
@@ -150,7 +150,7 @@ namespace Barotrauma
|
||||
|
||||
private CoroutineHandle disableTailCoroutine;
|
||||
|
||||
private readonly IEnumerable<Body> myBodies;
|
||||
private readonly List<Body> myBodies;
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||
@@ -306,7 +306,8 @@ namespace Barotrauma
|
||||
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
|
||||
myBodies.Add(Character.AnimController.Collider.FarseerBody);
|
||||
}
|
||||
|
||||
private CharacterParams.AIParams _aiParams;
|
||||
@@ -1837,7 +1838,7 @@ namespace Barotrauma
|
||||
if (!attack.IsValidTarget(target)) { return false; }
|
||||
if (target is ISerializableEntity se && target is Character)
|
||||
{
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { return false; }
|
||||
if (attack.Conditionals.Any(c => !c.TargetSelf && !c.Matches(se))) { return false; }
|
||||
}
|
||||
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { return false; }
|
||||
if (attack.Ranged)
|
||||
@@ -2182,10 +2183,22 @@ namespace Barotrauma
|
||||
float margin = MathHelper.PiOver4 * distanceFactor;
|
||||
if (angle < margin)
|
||||
{
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(weapon.SimPosition, target.SimPosition, myBodies, collisionCategories, allowInsideFixture: true);
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(weapon.SimPosition, Character.GetRelativeSimPosition(target), myBodies, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody != null)
|
||||
{
|
||||
if (target is MapEntity)
|
||||
{
|
||||
if (pickedBody.UserData is Submarine sub && sub == target.Submarine)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (target == pickedBody.UserData)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Character t = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
{
|
||||
|
||||
@@ -832,6 +832,8 @@ namespace Barotrauma
|
||||
if (container == null) { return 0; }
|
||||
if (!container.HasAccess(character)) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containableItem)) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer();
|
||||
if (rootContainer?.GetComponent<Fabricator>() != null || rootContainer?.GetComponent<Fabricator>() != null) { return 0; }
|
||||
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
if (isRestrictionsDefined)
|
||||
@@ -1088,6 +1090,7 @@ namespace Barotrauma
|
||||
|
||||
private void RespondToAttack(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
float minorDamageThreshold = 10;
|
||||
float healAmount = 0.0f;
|
||||
if (attacker != null)
|
||||
{
|
||||
@@ -1135,6 +1138,7 @@ namespace Barotrauma
|
||||
// Don't react to attackers that are outside of the sub (e.g. AoE attacks)
|
||||
return;
|
||||
}
|
||||
bool isAttackerInfected = false;
|
||||
bool isAttackerFightingEnemy = false;
|
||||
if (IsFriendly(attacker))
|
||||
{
|
||||
@@ -1155,10 +1159,14 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
|
||||
// Inform other NPCs
|
||||
if (cumulativeDamage > 1 || totalDamage >= 10)
|
||||
if (isAttackerInfected || cumulativeDamage > 1 || totalDamage >= minorDamageThreshold)
|
||||
{
|
||||
InformOtherNPCs(cumulativeDamage);
|
||||
if (GameMain.IsMultiplayer || !attacker.IsPlayer || Character.TeamID != attacker.TeamID)
|
||||
{
|
||||
InformOtherNPCs(cumulativeDamage);
|
||||
}
|
||||
}
|
||||
if (Character.IsBot)
|
||||
{
|
||||
@@ -1167,7 +1175,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.IsSecurity)
|
||||
{
|
||||
if (attacker.TeamID != Character.TeamID && cumulativeDamage > 1 || cumulativeDamage > 10)
|
||||
if (attacker.TeamID != Character.TeamID && cumulativeDamage > 1 || cumulativeDamage > minorDamageThreshold)
|
||||
{
|
||||
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.50f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
@@ -1181,26 +1189,8 @@ namespace Barotrauma
|
||||
Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
if (cumulativeDamage > 1 && attacker.TeamID != Character.TeamID)
|
||||
{
|
||||
// If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay.
|
||||
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage), attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't react to minor (accidental) dmg done by characters that are in the same team
|
||||
if (cumulativeDamage < 10)
|
||||
{
|
||||
if (!Character.IsSecurity && cumulativeDamage > 1)
|
||||
{
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 50), attacker, GetReactionTime() * 2);
|
||||
}
|
||||
}
|
||||
// If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay.
|
||||
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage), attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
|
||||
}
|
||||
if (!isAttackerFightingEnemy)
|
||||
{
|
||||
@@ -1242,13 +1232,13 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing, dmgThreshold: attacker.TeamID == Character.TeamID ? 50 : 10);
|
||||
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing);
|
||||
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
|
||||
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
|
||||
}
|
||||
}
|
||||
|
||||
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage, bool isWitnessing = false, float dmgThreshold = 10, bool allowOffensive = true)
|
||||
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage, bool isWitnessing = false)
|
||||
{
|
||||
if (!IsFriendly(attacker))
|
||||
{
|
||||
@@ -1268,6 +1258,17 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float dmgThreshold = attacker.TeamID == Character.TeamID ? 50 : minorDamageThreshold;
|
||||
if (isAttackerInfected)
|
||||
{
|
||||
cumulativeDamage = 100;
|
||||
}
|
||||
if (GameMain.IsSingleplayer && attacker.IsPlayer && Character.TeamID == attacker.TeamID)
|
||||
{
|
||||
// Bots in the player team never act aggressively in single player when attacked by the player
|
||||
dmgThreshold = minorDamageThreshold;
|
||||
return cumulativeDamage > dmgThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
|
||||
{
|
||||
// Outside or attacked from an unconnected submarine -> don't react.
|
||||
@@ -1279,17 +1280,17 @@ namespace Barotrauma
|
||||
isAttackerFightingEnemy = true;
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
else if (isWitnessing && Character.CombatAction != null && !c.IsSecurity)
|
||||
if (isWitnessing && Character.CombatAction != null && !c.IsSecurity)
|
||||
{
|
||||
return Character.CombatAction.WitnessReaction;
|
||||
}
|
||||
else if (attacker.IsPlayer && FindInstigator() is Character instigator)
|
||||
if (attacker.IsPlayer && FindInstigator() is Character instigator)
|
||||
{
|
||||
// The guards don't react when the player there's an instigator around
|
||||
// The guards don't react to player's aggressions when there's an instigator around
|
||||
isAttackerFightingEnemy = true;
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (instigator.CombatAction != null ? instigator.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
|
||||
}
|
||||
else if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable))
|
||||
if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable))
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
{
|
||||
@@ -1307,15 +1308,11 @@ namespace Barotrauma
|
||||
// Already targeting the attacker -> treat as a more serious threat.
|
||||
cumulativeDamage *= 2;
|
||||
}
|
||||
if (attackResult.Afflictions != null && attackResult.Afflictions.Any(a => a is AfflictionHusk))
|
||||
{
|
||||
cumulativeDamage = 100;
|
||||
}
|
||||
if (cumulativeDamage > dmgThreshold)
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
{
|
||||
return c.IsSecurity && allowOffensive ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Arrest;
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Arrest;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1838,7 +1835,7 @@ namespace Barotrauma
|
||||
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = HasDivingSuit(character);
|
||||
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.GetActiveObjectives<AIObjectiveFightIntruders>().Any();
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
if (isCurrentHull)
|
||||
{
|
||||
|
||||
@@ -51,19 +51,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current or the next node is in ladders.
|
||||
/// </summary>
|
||||
public bool InLadders =>
|
||||
currentPath != null && currentPath.CurrentNode != null &&
|
||||
(currentPath.CurrentNode.Ladders != null && currentPath.CurrentNode.Ladders.Item.IsInteractable(character) ||
|
||||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null && currentPath.NextNode.Ladders.Item.IsInteractable(character)));
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any node in the path is in stairs
|
||||
/// </summary>
|
||||
public bool InStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
|
||||
|
||||
public bool IsCurrentNodeLadder => currentPath?.CurrentNode?.Ladders != null && currentPath.CurrentNode.Ladders.Item.IsInteractable(character);
|
||||
|
||||
public bool IsNextNodeLadder => GetNextLadder() != null;
|
||||
|
||||
public bool IsNextLadderSameAsCurrent
|
||||
@@ -99,14 +93,24 @@ namespace Barotrauma
|
||||
base.Update(speed);
|
||||
float step = 1.0f / 60.0f;
|
||||
checkDoorsTimer -= step;
|
||||
buttonPressTimer -= step;
|
||||
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsOpen)
|
||||
{
|
||||
buttonPressTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonPressTimer -= step;
|
||||
}
|
||||
findPathTimer -= step;
|
||||
}
|
||||
|
||||
public void SetPath(SteeringPath path)
|
||||
{
|
||||
currentPath = path;
|
||||
if (path.Nodes.Any()) currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
|
||||
if (path.Nodes.Any())
|
||||
{
|
||||
currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
|
||||
}
|
||||
findPathTimer = Math.Min(findPathTimer, 1.0f);
|
||||
IsPathDirty = false;
|
||||
}
|
||||
@@ -124,15 +128,9 @@ namespace Barotrauma
|
||||
|
||||
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
|
||||
{
|
||||
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.state && !lastDoor.door.IsOpen)
|
||||
{
|
||||
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
|
||||
Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
|
||||
}
|
||||
// Have to use a variable here or resetting doesn't work.
|
||||
Vector2 addition = CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
|
||||
steering += addition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -328,7 +326,13 @@ namespace Barotrauma
|
||||
{
|
||||
CheckDoorsInPath();
|
||||
doorsChecked = true;
|
||||
}
|
||||
}
|
||||
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsOpen)
|
||||
{
|
||||
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
|
||||
Reset();
|
||||
return Vector2.Zero;
|
||||
}
|
||||
Vector2 pos = host.WorldPosition;
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
// Only humanoids can climb ladders
|
||||
@@ -378,7 +382,7 @@ namespace Barotrauma
|
||||
//at the same height as the waypoint
|
||||
if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < (collider.height / 2 + collider.radius) * 1.25f)
|
||||
{
|
||||
float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY;
|
||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||
if (heightFromFloor <= 0.0f)
|
||||
{
|
||||
diff.Y = Math.Max(diff.Y, 100);
|
||||
@@ -516,7 +520,7 @@ namespace Barotrauma
|
||||
return ConvertUnits.ToDisplayUnits(Math.Max(colliderSize.X, colliderSize.Y));
|
||||
}
|
||||
|
||||
private (Door door, bool state) lastDoor;
|
||||
private (Door door, bool shouldBeOpen) lastDoor;
|
||||
private float GetDoorCheckTime()
|
||||
{
|
||||
if (steering.LengthSquared() > 0)
|
||||
@@ -539,7 +543,6 @@ namespace Barotrauma
|
||||
WayPoint nextWaypoint = null;
|
||||
Door door = null;
|
||||
bool shouldBeOpen = false;
|
||||
|
||||
if (currentPath.Nodes.Count == 1)
|
||||
{
|
||||
door = currentPath.Nodes.First().ConnectedDoor;
|
||||
@@ -645,7 +648,7 @@ namespace Barotrauma
|
||||
});
|
||||
if (canAccess)
|
||||
{
|
||||
bool pressButton = buttonPressTimer <= 0 || lastDoor.door != door || lastDoor.state != shouldBeOpen;
|
||||
bool pressButton = buttonPressTimer <= 0 || lastDoor.door != door || lastDoor.shouldBeOpen != shouldBeOpen;
|
||||
if (door.HasIntegratedButtons)
|
||||
{
|
||||
if (pressButton && character.CanSeeTarget(door.Item))
|
||||
@@ -653,7 +656,7 @@ namespace Barotrauma
|
||||
if (door.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
lastDoor = (door, shouldBeOpen);
|
||||
buttonPressTimer = buttonPressCooldown;
|
||||
buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -671,7 +674,7 @@ namespace Barotrauma
|
||||
if (closestButton.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
lastDoor = (door, shouldBeOpen);
|
||||
buttonPressTimer = buttonPressCooldown;
|
||||
buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -697,7 +700,6 @@ namespace Barotrauma
|
||||
// The button is on the wrong side of the door or a wall
|
||||
currentPath.Unreachable = true;
|
||||
}
|
||||
lastDoor = (null, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Barotrauma
|
||||
public virtual bool IgnoreUnsafeHulls => false;
|
||||
public virtual bool AbandonWhenCannotCompleteSubjectives => true;
|
||||
public virtual bool AllowSubObjectiveSorting => false;
|
||||
public virtual bool ForceOrderPriority => true;
|
||||
public virtual bool PrioritizeIfSubObjectivesActive => false;
|
||||
|
||||
/// <summary>
|
||||
|
||||
+6
-5
@@ -85,11 +85,12 @@ namespace Barotrauma
|
||||
bool equip = item.GetComponent<Holdable>() != null ||
|
||||
item.AllowedSlots.Any(s => s != InvSlotType.Any) &&
|
||||
item.AllowedSlots.None(s =>
|
||||
s == InvSlotType.Card ||
|
||||
s == InvSlotType.Head ||
|
||||
s == InvSlotType.Headset ||
|
||||
s == InvSlotType.InnerClothes ||
|
||||
s == InvSlotType.OuterClothes);
|
||||
s == InvSlotType.Card ||
|
||||
s == InvSlotType.Head ||
|
||||
s == InvSlotType.Headset ||
|
||||
s == InvSlotType.InnerClothes ||
|
||||
s == InvSlotType.OuterClothes ||
|
||||
s == InvSlotType.HealthInterface);
|
||||
|
||||
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
|
||||
{
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
public override string Identifier { get; set; } = "cleanup items";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => false;
|
||||
public override bool ForceOrderPriority => false;
|
||||
protected override bool ForceOrderPriority => false;
|
||||
|
||||
public readonly List<Item> prioritizedItems = new List<Item>();
|
||||
|
||||
|
||||
+2
-16
@@ -117,7 +117,7 @@ namespace Barotrauma
|
||||
private float AimSpeed => HumanAIController.AimSpeed;
|
||||
private float AimAccuracy => HumanAIController.AimAccuracy;
|
||||
|
||||
private bool EnemyIsClose() => Enemy != null && character.CurrentHull != null && character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500;
|
||||
private bool EnemyIsClose() => Enemy != null && Enemy.CurrentHull != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < 300;
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -366,7 +366,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
bool isAllowedToSeekWeapons = !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest;
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest;
|
||||
if (!isAllowedToSeekWeapons)
|
||||
{
|
||||
if (WeaponComponent == null)
|
||||
@@ -1190,19 +1190,5 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//private float CalculateEnemyStrength()
|
||||
//{
|
||||
// float enemyStrength = 0;
|
||||
// AttackContext currentContext = character.GetAttackContext();
|
||||
// foreach (Limb limb in Enemy.AnimController.Limbs)
|
||||
// {
|
||||
// if (limb.attack == null) continue;
|
||||
// if (!limb.attack.IsValidContext(currentContext)) { continue; }
|
||||
// if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
|
||||
// enemyStrength += limb.attack.GetTotalDamage(false);
|
||||
// }
|
||||
// return enemyStrength;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -159,7 +159,8 @@ namespace Barotrauma
|
||||
{
|
||||
TargetName = container.Item.Name,
|
||||
AbortCondition = obj =>
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
|
||||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ namespace Barotrauma
|
||||
public Func<Item, bool> RemoveExistingPredicate { get; set; }
|
||||
public int? RemoveExistingMax { get; set; }
|
||||
public string AbandonGetItemDialogueIdentifier { get; set; }
|
||||
public Func<bool> AbandonGetItemDialogueCondition { get; set; }
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -106,6 +107,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref getItemObjective,
|
||||
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip)
|
||||
{
|
||||
CannotFindDialogueCondition = AbandonGetItemDialogueCondition,
|
||||
CannotFindDialogueIdentifierOverride = AbandonGetItemDialogueIdentifier,
|
||||
SpeakIfFails = AbandonGetItemDialogueIdentifier != null,
|
||||
TakeWholeStack = this.TakeWholeStack
|
||||
|
||||
+2
@@ -26,6 +26,7 @@ namespace Barotrauma
|
||||
if (totalEnemies == 0) { return 0; }
|
||||
if (character.IsSecurity) { return 100; }
|
||||
if (objectiveManager.IsOrder(this)) { return 100; }
|
||||
// If there's any security officers onboard, leave fighting for them.
|
||||
return HumanAIController.IsTrueForAnyCrewMember(c => c.Character.IsSecurity && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine) ? 0 : 100;
|
||||
}
|
||||
|
||||
@@ -64,6 +65,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 (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
return true;
|
||||
|
||||
+4
-1
@@ -162,7 +162,10 @@ namespace Barotrauma
|
||||
CloseEnough = reach,
|
||||
DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null,
|
||||
TargetName = Leak.FlowTargetHull?.DisplayName,
|
||||
CheckVisibility = false
|
||||
CheckVisibility = false,
|
||||
requiredCondition = () => Leak.Submarine == character.Submarine,
|
||||
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
|
||||
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
|
||||
+9
-12
@@ -59,6 +59,7 @@ namespace Barotrauma
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool SpeakIfFails { get; set; }
|
||||
public string CannotFindDialogueIdentifierOverride { get; set; }
|
||||
public Func<bool> CannotFindDialogueCondition { get; set; }
|
||||
|
||||
private int _itemCount = 1;
|
||||
public int ItemCount
|
||||
@@ -560,22 +561,18 @@ namespace Barotrauma
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
#endif
|
||||
}
|
||||
if (SpeakIfFails)
|
||||
{
|
||||
SpeakCannotFind();
|
||||
}
|
||||
SpeakCannotFind();
|
||||
}
|
||||
|
||||
private void SpeakCannotFind()
|
||||
{
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string msg = TextManager.Get(CannotFindDialogueIdentifierOverride, returnNull: true) ?? TextManager.Get("dialogcannotfinditem", returnNull: true);
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotfinditem", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
if (!SpeakIfFails) { return; }
|
||||
if (!character.IsOnPlayerTeam) { return; }
|
||||
if (objectiveManager.CurrentOrder != objectiveManager.CurrentObjective) { return; }
|
||||
if (CannotFindDialogueCondition != null && !CannotFindDialogueCondition()) { return; }
|
||||
string msg = TextManager.Get(CannotFindDialogueIdentifierOverride, returnNull: true) ?? TextManager.Get("dialogcannotfinditem", returnNull: true);
|
||||
if (msg == null) { return; }
|
||||
character.Speak(msg, identifier: "dialogcannotfinditem", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-18
@@ -96,6 +96,8 @@ namespace Barotrauma
|
||||
|
||||
public float? OverridePriority = null;
|
||||
|
||||
public Func<bool> SpeakCannotReachCondition { get; set; }
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
@@ -166,14 +168,14 @@ namespace Barotrauma
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
}
|
||||
#endif
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective && DialogueIdentifier != null && SpeakIfFails)
|
||||
{
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
if (!character.IsOnPlayerTeam) { return; }
|
||||
if (objectiveManager.CurrentOrder != objectiveManager.CurrentObjective) { return; }
|
||||
if (DialogueIdentifier == null) { return; }
|
||||
if (!SpeakIfFails) { return; }
|
||||
if (SpeakCannotReachCondition != null && !SpeakCannotReachCondition()) { return; }
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
|
||||
if (msg == null) { return; }
|
||||
character.Speak(msg, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
|
||||
public void ForceAct(float deltaTime) => Act(deltaTime);
|
||||
@@ -635,21 +637,27 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath?.CurrentNode?.Ladders != null)
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
|
||||
{
|
||||
//don't consider the character to be close enough to the target while climbing ladders,
|
||||
//UNLESS the last node in the path has been reached
|
||||
//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; }
|
||||
// Climbing a ladder
|
||||
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)
|
||||
{
|
||||
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;
|
||||
float yDist = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (yDist > CloseEnough) { return false; }
|
||||
float xDist = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
|
||||
return xDist <= CloseEnough;
|
||||
}
|
||||
|
||||
Vector2 sourcePos = UseDistanceRelativeToAimSourcePos ? character.AnimController.AimSourceWorldPos : character.WorldPosition;
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, sourcePos) < CloseEnough * CloseEnough;
|
||||
}
|
||||
|
||||
+115
-5
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -20,6 +21,8 @@ namespace Barotrauma
|
||||
private Item Container { get; }
|
||||
private ItemContainer ItemContainer { get; }
|
||||
private ImmutableArray<string> TargetContainerTags { get; }
|
||||
private ImmutableHashSet<string> ValidContainableItemIdentifiers { get; }
|
||||
private static Dictionary<ItemPrefab, ImmutableHashSet<string>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<string>>();
|
||||
|
||||
private int itemIndex = 0;
|
||||
private AIObjectiveDecontainItem decontainObjective;
|
||||
@@ -47,6 +50,109 @@ namespace Barotrauma
|
||||
abandonGetItemDialogueIdentifier = optionSpecificDialogueIdentifier;
|
||||
}
|
||||
}
|
||||
ValidContainableItemIdentifiers = GetValidContainableItemIdentifiers();
|
||||
if (ValidContainableItemIdentifiers.None())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ShowError($"No valid containable item identifiers found for the Load Item objective targeting {Container}");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private enum CheckStatus { Unfinished, Finished }
|
||||
|
||||
private ImmutableHashSet<string> GetValidContainableItemIdentifiers()
|
||||
{
|
||||
if (AllValidContainableItemIdentifiers.TryGetValue(Container.Prefab, out var existingIdentifiers))
|
||||
{
|
||||
return existingIdentifiers;
|
||||
}
|
||||
// Status effects are often used to alter item condition so using the Containable Item Identifiers directly can lead to unwanted results
|
||||
// For example, placing welding fuel tanks inside oxygen tank shelves
|
||||
bool defaultContainableItemIdentifiers = true;
|
||||
var potentialContainablePrefabs = MapEntityPrefab.List
|
||||
.Where(mep => mep is ItemPrefab ip && ItemContainer.ContainableItemIdentifiers.Any(i => i == ip.Identifier || ip.Tags.Contains(i)))
|
||||
.Cast<ItemPrefab>();
|
||||
var validContainableItemIdentifiers = new HashSet<string>();
|
||||
foreach (var component in Container.Components)
|
||||
{
|
||||
if (CheckComponent() == CheckStatus.Finished)
|
||||
{
|
||||
break;
|
||||
}
|
||||
CheckStatus CheckComponent()
|
||||
{
|
||||
if (component.statusEffectLists != null)
|
||||
{
|
||||
foreach (var (_, statusEffects) in component.statusEffectLists)
|
||||
{
|
||||
if (CheckStatusEffects(statusEffects) == CheckStatus.Finished)
|
||||
{
|
||||
return CheckStatus.Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (component is ItemContainer itemContainer && itemContainer.ContainableItems != null)
|
||||
{
|
||||
foreach (var item in itemContainer.ContainableItems)
|
||||
{
|
||||
if (CheckStatusEffects(item.statusEffects) == CheckStatus.Finished)
|
||||
{
|
||||
return CheckStatus.Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CheckStatus.Unfinished;
|
||||
CheckStatus CheckStatusEffects(IEnumerable<StatusEffect> statusEffects)
|
||||
{
|
||||
if (statusEffects == null) { return CheckStatus.Unfinished; }
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
if ((statusEffect.TargetIdentifiers == null || statusEffect.TargetIdentifiers.None()) && !statusEffect.HasConditions) { continue; }
|
||||
switch (TargetItemCondition)
|
||||
{
|
||||
case AIObjectiveLoadItems.ItemCondition.Empty:
|
||||
if (!statusEffect.ReducesItemCondition()) { continue; }
|
||||
break;
|
||||
case AIObjectiveLoadItems.ItemCondition.Full:
|
||||
if (!statusEffect.IncreasesItemCondition()) { continue; }
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
defaultContainableItemIdentifiers = false;
|
||||
if (statusEffect.TargetIdentifiers != null)
|
||||
{
|
||||
foreach (string target in statusEffect.TargetIdentifiers)
|
||||
{
|
||||
foreach (var prefab in potentialContainablePrefabs)
|
||||
{
|
||||
if (CheckPrefab(prefab, () => prefab.Tags.Contains(target)) == CheckStatus.Finished) { return CheckStatus.Finished; }
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var prefab in potentialContainablePrefabs)
|
||||
{
|
||||
if (CheckPrefab(prefab, () => statusEffect.MatchesTagConditionals(prefab)) == CheckStatus.Finished) { return CheckStatus.Finished; }
|
||||
}
|
||||
CheckStatus CheckPrefab(ItemPrefab prefab, Func<bool> isValid)
|
||||
{
|
||||
if (validContainableItemIdentifiers.Contains(prefab.Identifier)) { return CheckStatus.Unfinished; }
|
||||
if (!isValid()) { return CheckStatus.Unfinished; }
|
||||
validContainableItemIdentifiers.Add(prefab.Identifier);
|
||||
if (potentialContainablePrefabs.Any(p => !validContainableItemIdentifiers.Contains(p.Identifier))) { return CheckStatus.Unfinished; }
|
||||
return CheckStatus.Finished;
|
||||
}
|
||||
}
|
||||
return CheckStatus.Unfinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
var newIdentifiers = defaultContainableItemIdentifiers ? ItemContainer.ContainableItemIdentifiers.ToImmutableHashSet() : validContainableItemIdentifiers.ToImmutableHashSet();
|
||||
AllValidContainableItemIdentifiers.Add(Container.Prefab, newIdentifiers);
|
||||
return newIdentifiers;
|
||||
}
|
||||
|
||||
protected override float GetPriority()
|
||||
@@ -116,7 +222,7 @@ namespace Barotrauma
|
||||
base.Update(deltaTime);
|
||||
if (targetItem == null)
|
||||
{
|
||||
if (character.FindItem(ref itemIndex, out Item item, identifiers: ItemContainer.ContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetConditionBasedPriority))
|
||||
if (character.FindItem(ref itemIndex, out Item item, identifiers: ValidContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetPriority))
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
@@ -125,17 +231,19 @@ namespace Barotrauma
|
||||
}
|
||||
targetItem = item;
|
||||
}
|
||||
// Prefer items closer to full condition when target condition is Empty, and vice versa
|
||||
float GetConditionBasedPriority(Item item)
|
||||
float GetPriority(Item item)
|
||||
{
|
||||
try
|
||||
{
|
||||
return TargetItemCondition switch
|
||||
// Prefer items closer to full condition when target condition is Empty, and vice versa
|
||||
float conditionBasedPriority = TargetItemCondition switch
|
||||
{
|
||||
AIObjectiveLoadItems.ItemCondition.Full => MathUtils.InverseLerp(100.0f, 0.0f, item.ConditionPercentage),
|
||||
AIObjectiveLoadItems.ItemCondition.Empty => MathUtils.InverseLerp(0.0f, 100.0f, item.ConditionPercentage),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
// Prefer items that have the same identifier as one of the already contained items
|
||||
return ItemContainer.ContainsItemsWithSameIdentifier(item) ? conditionBasedPriority : conditionBasedPriority / 2;
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
@@ -161,10 +269,11 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref decontainObjective,
|
||||
constructor: () => new AIObjectiveDecontainItem(character, targetItem, objectiveManager, targetContainer: ItemContainer, priorityModifier: PriorityModifier)
|
||||
{
|
||||
AbandonGetItemDialogueCondition = () => IsValidContainable(targetItem),
|
||||
AbandonGetItemDialogueIdentifier = abandonGetItemDialogueIdentifier,
|
||||
Equip = true,
|
||||
RemoveExistingWhenNecessary = true,
|
||||
RemoveExistingPredicate = (i) => AIObjectiveLoadItems.ItemMatchesTargetCondition(i, TargetItemCondition),
|
||||
RemoveExistingPredicate = (i) => !ValidContainableItemIdentifiers.Contains(i.Prefab.Identifier) || AIObjectiveLoadItems.ItemMatchesTargetCondition(i, TargetItemCondition),
|
||||
RemoveExistingMax = 1
|
||||
},
|
||||
onCompleted: () =>
|
||||
@@ -189,6 +298,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.Removed) { return false; }
|
||||
if (!ValidContainableItemIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
var rootInventoryOwner = item.GetRootInventoryOwner();
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Barotrauma
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
protected virtual bool ResetWhenClearingIgnoreList => true;
|
||||
protected virtual bool ForceOrderPriority => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
|
||||
+6
-1
@@ -643,7 +643,12 @@ namespace Barotrauma
|
||||
|
||||
public bool IsOrder(AIObjective objective)
|
||||
{
|
||||
return objective == ForcedOrder || CurrentOrders.Any(o => o.Objective == objective);
|
||||
if (objective == ForcedOrder) { return true; }
|
||||
foreach (var order in CurrentOrders)
|
||||
{
|
||||
if (order.Objective == objective) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasOrders()
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ namespace Barotrauma
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
if (pump.Item.CurrentHull == null) { return false; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (pump.IsAutoControlled) { return false; }
|
||||
if (pump.Item.ConditionPercentage <= 0) { return false; }
|
||||
if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
if (character.Submarine != null)
|
||||
|
||||
+1
-1
@@ -489,7 +489,7 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public static IEnumerable<Affliction> GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
|
||||
public static IEnumerable<Affliction> GetSortedAfflictions(Character character, bool excludeBuffs = true) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions(), excludeBuffs);
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
|
||||
{
|
||||
|
||||
@@ -374,7 +374,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (OptionNames.Count != Options.Length)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Order " + Name + " - the number of option names doesn't match the number of options.");
|
||||
DebugConsole.AddWarning("Error in Order " + Name + " - the number of option names doesn't match the number of options.");
|
||||
OptionNames.Clear();
|
||||
Options.ForEach(o => OptionNames.Add(o, o));
|
||||
}
|
||||
@@ -499,16 +499,14 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", int? priority = null)
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", bool isNewOrder = true)
|
||||
{
|
||||
priority ??= CharacterInfo.HighestManualOrderPriority;
|
||||
// If the order has a lesser priority, it means we are rearranging character orders
|
||||
if (!TargetAllCharacters && priority != CharacterInfo.HighestManualOrderPriority && Identifier != "dismissed")
|
||||
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;
|
||||
}
|
||||
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}";
|
||||
messageTag += $".{Identifier}";
|
||||
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}.{Identifier}";
|
||||
if (!string.IsNullOrEmpty(orderOption))
|
||||
{
|
||||
if (Identifier != "dismissed")
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ namespace Barotrauma
|
||||
CommandingCharacter.Speak(SuggestedOrderPrefab.GetChatMessage(OrderedCharacter.Name, "", false), minDurationBetweenSimilar: 5);
|
||||
}
|
||||
CurrentOrder = new Order(SuggestedOrderPrefab, TargetItem, TargetItemComponent, CommandingCharacter);
|
||||
OrderedCharacter.SetOrder(CurrentOrder, Option, priority: 3, CommandingCharacter, CommandingCharacter != OrderedCharacter);
|
||||
OrderedCharacter.SetOrder(CurrentOrder, Option, priority: CharacterInfo.HighestManualOrderPriority, CommandingCharacter, CommandingCharacter != OrderedCharacter);
|
||||
OrderedCharacter.Speak(TextManager.Get("DialogAffirmative"), delay: 1.0f, minDurationBetweenSimilar: 5);
|
||||
}
|
||||
TimeSinceLastAttempt = 0f;
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (!Active) { return; }
|
||||
if (!Active || character.IsArrested) { return; }
|
||||
decisionTimer -= deltaTime;
|
||||
if (decisionTimer <= 0.0f)
|
||||
{
|
||||
@@ -344,7 +344,6 @@ namespace Barotrauma
|
||||
|
||||
ShipIssueWorkers.Clear();
|
||||
|
||||
// could have support for multiple reactors, todo m61
|
||||
if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
ShipIssueWorkers.Add(new ShipIssueWorkerPowerUpReactor(this, Order.GetPrefab("operatereactor"), reactor.Item, reactor, "powerup"));
|
||||
|
||||
@@ -269,6 +269,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float GetHeightFromFloor() => GetColliderBottom().Y - FloorY;
|
||||
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
public bool IsAboveFloor => GetHeightFromFloor() > -0.1f;
|
||||
|
||||
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
useItemTimer = 0.5f;
|
||||
@@ -332,7 +337,7 @@ namespace Barotrauma
|
||||
aimingMelee = aimMelee;
|
||||
if (character.Stun > 0.0f || character.IsIncapacitated)
|
||||
{
|
||||
aim = false;
|
||||
aim = false;
|
||||
}
|
||||
|
||||
//calculate the handle positions
|
||||
|
||||
+43
-39
@@ -382,51 +382,55 @@ namespace Barotrauma
|
||||
mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
mouthLimb.body.ApplyTorque(-force * 50);
|
||||
}
|
||||
var jaw = GetLimb(LimbType.Jaw);
|
||||
if (jaw != null)
|
||||
{
|
||||
jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
|
||||
}
|
||||
|
||||
character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
|
||||
float particleFrequency = MathHelper.Clamp(eatSpeed / 2, 0.02f, 0.5f);
|
||||
if (Rand.Value() < particleFrequency / 6)
|
||||
if (Character.CanEat)
|
||||
{
|
||||
target.AnimController.MainLimb.AddDamage(target.SimPosition, dmg, 0, 0, false);
|
||||
}
|
||||
if (Rand.Value() < particleFrequency)
|
||||
{
|
||||
target.AnimController.MainLimb.AddDamage(target.SimPosition, 0, dmg, 0, false);
|
||||
}
|
||||
if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
|
||||
{
|
||||
static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered;
|
||||
//keep severing joints until there is only one limb left
|
||||
var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
|
||||
if (nonSeveredJoints.None())
|
||||
var jaw = GetLimb(LimbType.Jaw);
|
||||
if (jaw != null)
|
||||
{
|
||||
//small monsters don't eat the contents of the character's inventory
|
||||
if (Mass < target.AnimController.Mass)
|
||||
{
|
||||
target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null));
|
||||
}
|
||||
|
||||
//only one limb left, the character is now full eaten
|
||||
Entity.Spawner?.AddToRemoveQueue(target);
|
||||
|
||||
if (Character.AIController is EnemyAIController enemyAi)
|
||||
{
|
||||
enemyAi.PetBehavior?.OnEat("dead", 1.0f);
|
||||
}
|
||||
|
||||
character.SelectedCharacter = null;
|
||||
jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
|
||||
}
|
||||
else //sever a random joint
|
||||
|
||||
character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
|
||||
float particleFrequency = MathHelper.Clamp(eatSpeed / 2, 0.02f, 0.5f);
|
||||
if (Rand.Value() < particleFrequency / 6)
|
||||
{
|
||||
target.AnimController.SeverLimbJoint(nonSeveredJoints.GetRandom());
|
||||
target.AnimController.MainLimb.AddDamage(target.SimPosition, dmg, 0, 0, false);
|
||||
}
|
||||
}
|
||||
if (Rand.Value() < particleFrequency)
|
||||
{
|
||||
target.AnimController.MainLimb.AddDamage(target.SimPosition, 0, dmg, 0, false);
|
||||
}
|
||||
if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
|
||||
{
|
||||
static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered;
|
||||
//keep severing joints until there is only one limb left
|
||||
var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
|
||||
if (nonSeveredJoints.None())
|
||||
{
|
||||
//small monsters don't eat the contents of the character's inventory
|
||||
if (Mass < target.AnimController.Mass)
|
||||
{
|
||||
target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null));
|
||||
}
|
||||
|
||||
//only one limb left, the character is now full eaten
|
||||
Entity.Spawner?.AddToRemoveQueue(target);
|
||||
|
||||
if (Character.AIController is EnemyAIController enemyAi)
|
||||
{
|
||||
enemyAi.PetBehavior?.OnEat("dead", 1.0f);
|
||||
}
|
||||
|
||||
character.SelectedCharacter = null;
|
||||
}
|
||||
else //sever a random joint
|
||||
{
|
||||
target.AnimController.SeverLimbJoint(nonSeveredJoints.GetRandom());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+4
-4
@@ -808,7 +808,8 @@ namespace Barotrauma
|
||||
if (head == null) { return; }
|
||||
if (torso == null) { return; }
|
||||
|
||||
if (currentHull != null)
|
||||
//check both hulls: the hull whose coordinate space the ragdoll is in, and the hull whose bounds the character's origin actually is inside
|
||||
if (currentHull != null && character.CurrentHull != null)
|
||||
{
|
||||
float surfacePos = currentHull.Surface;
|
||||
float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f);
|
||||
@@ -816,7 +817,7 @@ namespace Barotrauma
|
||||
//and use its water surface instead of the current hull's
|
||||
if (currentHull.Rect.Y - currentHull.Surface < 5.0f)
|
||||
{
|
||||
GetSurfacePos(CurrentHull, ref surfacePos);
|
||||
GetSurfacePos(currentHull, ref surfacePos);
|
||||
void GetSurfacePos(Hull hull, ref float prevSurfacePos)
|
||||
{
|
||||
if (prevSurfacePos > surfaceThreshold) { return; }
|
||||
@@ -834,7 +835,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var linkedTo in gap.linkedTo)
|
||||
{
|
||||
if (linkedTo is Hull otherHull && otherHull != hull)
|
||||
if (linkedTo is Hull otherHull && otherHull != hull && otherHull != currentHull)
|
||||
{
|
||||
prevSurfacePos = Math.Max(surfacePos, otherHull.Surface);
|
||||
GetSurfacePos(otherHull, ref prevSurfacePos);
|
||||
@@ -888,7 +889,6 @@ namespace Barotrauma
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 diff = (mousePos - torso.SimPosition) * Dir;
|
||||
TargetMovement = new Vector2(0.0f, -0.1f);
|
||||
float newRotation = MathUtils.VectorToAngle(diff);
|
||||
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
|
||||
@@ -130,6 +130,9 @@ namespace Barotrauma
|
||||
set => _structureDamage = value;
|
||||
}
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool EmitStructureDamageParticles { get; private set; }
|
||||
|
||||
private float _itemDamage;
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float ItemDamage
|
||||
@@ -311,7 +314,7 @@ namespace Barotrauma
|
||||
return totalDamage * DamageMultiplier;
|
||||
}
|
||||
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range = 0.0f, float penetration = 0f)
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range = 0.0f)
|
||||
{
|
||||
if (damage > 0.0f) Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage), null);
|
||||
if (bleedingDamage > 0.0f) Afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage), null);
|
||||
|
||||
@@ -682,8 +682,8 @@ namespace Barotrauma
|
||||
get { return CharacterHealth.BloodlossAmount; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
CharacterHealth.BloodlossAmount = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
CharacterHealth.BloodlossAmount = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1830,7 +1830,7 @@ namespace Barotrauma
|
||||
if (!attack.IsValidTarget(attackTarget)) { return false; }
|
||||
if (attackTarget is ISerializableEntity se && attackTarget is Character)
|
||||
{
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { return false; }
|
||||
if (attack.Conditionals.Any(c => !c.TargetSelf && !c.Matches(se))) { return false; }
|
||||
}
|
||||
}
|
||||
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(this))) { return false; }
|
||||
@@ -2270,17 +2270,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem == item ||
|
||||
HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; }
|
||||
|
||||
Pickable pickableComponent = item.GetComponent<Pickable>();
|
||||
if (pickableComponent != null && pickableComponent.Picker != this && pickableComponent.Picker != null && !pickableComponent.Picker.IsDead) { return false; }
|
||||
|
||||
if (SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
|
||||
//optimization: don't use HeldItems because it allocates memory and this method is executed very frequently
|
||||
var heldItem1 = Inventory?.GetItemInLimbSlot(InvSlotType.RightHand);
|
||||
if (heldItem1?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
|
||||
var heldItem2 = Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand);
|
||||
if (heldItem2?.GetComponent<RemoteController>()?.TargetItem == item) { return true; }
|
||||
|
||||
Vector2 characterDirection = Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(AnimController.Collider.Rotation));
|
||||
|
||||
Vector2 upperBodyPosition = Position + (characterDirection * 20.0f);
|
||||
@@ -3225,7 +3226,7 @@ namespace Barotrauma
|
||||
|
||||
if (orderGiver != null)
|
||||
{
|
||||
var abilityOrderedCharacter = new AbilityCharacter(this);
|
||||
var abilityOrderedCharacter = new AbilityOrderedCharacter(this);
|
||||
orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter);
|
||||
|
||||
if (orderGiver.LastOrderedCharacter != this)
|
||||
@@ -3547,12 +3548,12 @@ 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: attacker == null || attacker.IsHuman || attacker.IsPlayer);
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: attacker == null || attacker.IsHuman || attacker.IsPlayer, attacker: attacker);
|
||||
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading)
|
||||
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, Character attacker = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
#if DEBUG
|
||||
@@ -3590,9 +3591,20 @@ namespace Barotrauma
|
||||
if (severed)
|
||||
{
|
||||
Limb otherLimb = joint.LimbA == targetLimb ? joint.LimbB : joint.LimbA;
|
||||
otherLimb.body.ApplyLinearImpulse(targetLimb.LinearVelocity * targetLimb.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
|
||||
otherLimb.body.ApplyLinearImpulse(targetLimb.LinearVelocity * targetLimb.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
|
||||
if (attacker != null)
|
||||
{
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
if (statusEffect.type == ActionType.OnSevered) { statusEffect.SetUser(attacker); }
|
||||
}
|
||||
foreach (var statusEffect in targetLimb.StatusEffects)
|
||||
{
|
||||
if (statusEffect.type == ActionType.OnSevered) { statusEffect.SetUser(attacker); }
|
||||
}
|
||||
}
|
||||
ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
targetLimb.ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
targetLimb.ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
}
|
||||
}
|
||||
if (wasSevered && targetLimb.character.AIController is EnemyAIController enemyAI)
|
||||
@@ -3961,8 +3973,8 @@ namespace Barotrauma
|
||||
causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource);
|
||||
OnDeath?.Invoke(this, CauseOfDeath);
|
||||
|
||||
var abilityKiller = new AbilityCharacter(CauseOfDeath.Killer);
|
||||
CheckTalents(AbilityEffectType.OnDieToCharacter, abilityKiller);
|
||||
var abilityCharacterKiller = new AbilityCharacterKiller(CauseOfDeath.Killer);
|
||||
CheckTalents(AbilityEffectType.OnDieToCharacter, abilityCharacterKiller);
|
||||
|
||||
if (GameMain.GameSession != null && Screen.Selected == GameMain.GameScreen)
|
||||
{
|
||||
@@ -4472,18 +4484,17 @@ namespace Barotrauma
|
||||
if (info == null) { return false; }
|
||||
info.UnlockedTalents.Add(talentPrefab.Identifier);
|
||||
if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; }
|
||||
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
|
||||
#endif
|
||||
CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this);
|
||||
characterTalent.ActivateTalent(addingFirstTime);
|
||||
characterTalents.Add(characterTalent);
|
||||
characterTalent.ActivateTalent(addingFirstTime);
|
||||
characterTalent.AddedThisRound = addingFirstTime;
|
||||
|
||||
if (addingFirstTime)
|
||||
{
|
||||
OnTalentGiven(talentPrefab.Identifier);
|
||||
OnTalentGiven(talentPrefab);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4493,6 +4504,24 @@ namespace Barotrauma
|
||||
return info.UnlockedTalents.Contains(identifier);
|
||||
}
|
||||
|
||||
public bool HasUnlockedAllTalents()
|
||||
{
|
||||
if (TalentTree.JobTalentTrees.TryGetValue(Info.Job.Prefab.Identifier, out TalentTree talentTree))
|
||||
{
|
||||
foreach (TalentSubTree talentSubTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
|
||||
{
|
||||
if (talentOption.Talents.None(t => HasTalent(t.Identifier)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<Character> GetFriendlyCrew(Character character)
|
||||
{
|
||||
if (character is null)
|
||||
@@ -4552,7 +4581,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void OnMoneyChanged(int prevAmount, int newAmount);
|
||||
partial void OnTalentGiven(string talentIdentifier);
|
||||
partial void OnTalentGiven(TalentPrefab talentPrefab);
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary is used for stats that are required very frequently. Not very performant, but easier to develop with for now.
|
||||
@@ -4724,4 +4753,49 @@ namespace Barotrauma
|
||||
public Character Killer { get; set; }
|
||||
}
|
||||
|
||||
class AbilityAttackData : AbilityObject, IAbilityCharacter
|
||||
{
|
||||
public float DamageMultiplier { get; set; } = 1f;
|
||||
public float AddedPenetration { get; set; } = 0f;
|
||||
public List<Affliction> Afflictions { get; set; }
|
||||
public bool ShouldImplode { get; set; } = false;
|
||||
public Attack SourceAttack { get; }
|
||||
public Character Character { get; set; }
|
||||
public Character Attacker { get; set; }
|
||||
|
||||
public AbilityAttackData(Attack sourceAttack, Character character)
|
||||
{
|
||||
SourceAttack = sourceAttack;
|
||||
Character = character;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityAttackResult : AbilityObject, IAbilityAttackResult
|
||||
{
|
||||
public AttackResult AttackResult { get; set; }
|
||||
|
||||
public AbilityAttackResult(AttackResult attackResult)
|
||||
{
|
||||
AttackResult = attackResult;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityCharacterKiller : AbilityObject, IAbilityCharacter
|
||||
{
|
||||
public AbilityCharacterKiller(Character character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
class AbilityOrderedCharacter : AbilityObject, IAbilityCharacter
|
||||
{
|
||||
public AbilityOrderedCharacter(Character character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ namespace Barotrauma
|
||||
|
||||
public CharacterTeamType TeamID;
|
||||
|
||||
private readonly NPCPersonalityTrait personalityTrait;
|
||||
private NPCPersonalityTrait personalityTrait;
|
||||
|
||||
public const int MaxCurrentOrders = 3;
|
||||
public static int HighestManualOrderPriority => MaxCurrentOrders;
|
||||
@@ -568,7 +568,7 @@ namespace Barotrauma
|
||||
HasGenders = CharacterConfigElement.GetAttributeBool("genders", false);
|
||||
HasRaces = CharacterConfigElement.GetAttributeBool("races", false);
|
||||
SetGenderAndRace(randSync);
|
||||
Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant);
|
||||
Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, randSync, variant);
|
||||
HairColors = CharacterConfigElement.GetAttributeTupleArray("haircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
|
||||
FacialHairColors = CharacterConfigElement.GetAttributeTupleArray("facialhaircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
|
||||
SkinColors = CharacterConfigElement.GetAttributeTupleArray("skincolors", new (Color, float)[] { (new Color(255, 215, 200, 255), 100f) }).ToImmutableArray();
|
||||
@@ -584,11 +584,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "";
|
||||
Name = GetRandomName(randSync);
|
||||
}
|
||||
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
|
||||
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
|
||||
SetPersonalityTrait();
|
||||
Salary = CalculateSalary();
|
||||
if (ragdollFileName != null)
|
||||
{
|
||||
@@ -597,6 +596,11 @@ namespace Barotrauma
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
|
||||
private void SetPersonalityTrait()
|
||||
{
|
||||
personalityTrait = NPCPersonalityTrait.GetRandom(Name + HeadSpriteId);
|
||||
}
|
||||
|
||||
public string GetRandomName(Rand.RandSync randSync)
|
||||
{
|
||||
string name = "";
|
||||
@@ -1261,7 +1265,7 @@ namespace Barotrauma
|
||||
{
|
||||
int prevAmount = ExperiencePoints;
|
||||
|
||||
var experienceGainMultiplier = new AbilityValue(1f);
|
||||
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
|
||||
if (isMissionExperience)
|
||||
{
|
||||
Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
|
||||
@@ -1858,18 +1862,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
class AbilitySkillGain : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
|
||||
class AbilitySkillGain : AbilityObject, IAbilityValue, IAbilitySkillIdentifier, IAbilityCharacter
|
||||
{
|
||||
public AbilitySkillGain(float value, string abilityString, Character character, bool gainedFromAbility)
|
||||
public AbilitySkillGain(float skillAmount, string skillIdentifier, Character character, bool gainedFromAbility)
|
||||
{
|
||||
Value = value;
|
||||
String = abilityString;
|
||||
Value = skillAmount;
|
||||
SkillIdentifier = skillIdentifier;
|
||||
Character = character;
|
||||
GainedFromAbility = gainedFromAbility;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public float Value { get; set; }
|
||||
public string String { get; set; }
|
||||
public string SkillIdentifier { get; set; }
|
||||
public bool GainedFromAbility { get; }
|
||||
}
|
||||
|
||||
class AbilityExperienceGainMultiplier : AbilityObject, IAbilityValue
|
||||
{
|
||||
public AbilityExperienceGainMultiplier(float experienceGainMultiplier)
|
||||
{
|
||||
Value = experienceGainMultiplier;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace Barotrauma
|
||||
|
||||
public Affliction(AfflictionPrefab prefab, float strength)
|
||||
{
|
||||
#if CLIENT
|
||||
prefab?.ReloadSoundsIfNeeded();
|
||||
#endif
|
||||
Prefab = prefab;
|
||||
PendingAdditionStrength = Prefab.GrainBurst;
|
||||
_strength = strength;
|
||||
|
||||
+5
-5
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AfflictionBleeding : Affliction
|
||||
{
|
||||
@@ -15,6 +11,10 @@ namespace Barotrauma
|
||||
{
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
characterHealth.BloodlossAmount += Strength * (1.0f / 60.0f) * deltaTime;
|
||||
if (Source != null)
|
||||
{
|
||||
characterHealth.BloodlossAffliction.Source = Source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -52,10 +52,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (state == value) { return; }
|
||||
state = value;
|
||||
if (character != null && character == Character.Controlled)
|
||||
{
|
||||
UpdateMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +77,9 @@ namespace Barotrauma
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
character = characterHealth.Character;
|
||||
if (character == null) { return; }
|
||||
|
||||
UpdateMessages();
|
||||
|
||||
if (!subscribedToDeathEvent)
|
||||
{
|
||||
character.OnDeath += CharacterDead;
|
||||
|
||||
+29
-1
@@ -363,6 +363,7 @@ namespace Barotrauma
|
||||
public readonly string Name, Description;
|
||||
public readonly string TranslationOverride;
|
||||
public readonly bool IsBuff;
|
||||
public readonly float HealCostMultiplier;
|
||||
|
||||
public readonly string CauseOfDeathDescription, SelfCauseOfDeathDescription;
|
||||
|
||||
@@ -655,6 +656,7 @@ namespace Barotrauma
|
||||
Name = TextManager.Get("AfflictionName." + translationId, true) ?? element.GetAttributeString("name", "");
|
||||
Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", "");
|
||||
IsBuff = element.GetAttributeBool("isbuff", false);
|
||||
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier).ToLowerInvariant(), 1f);
|
||||
|
||||
if (element.Attribute("nameidentifier") != null)
|
||||
{
|
||||
@@ -677,7 +679,7 @@ namespace Barotrauma
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLowerInvariant(), 0.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : 0.05f));
|
||||
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
|
||||
@@ -751,6 +753,32 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public void ReloadSoundsIfNeeded()
|
||||
{
|
||||
foreach (var effect in effects)
|
||||
{
|
||||
foreach (var statusEffect in effect.StatusEffects)
|
||||
{
|
||||
foreach (var sound in statusEffect.Sounds)
|
||||
{
|
||||
if (sound.Sound == null) { Submarine.ReloadRoundSound(sound); }
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var periodicEffect in periodicEffects)
|
||||
{
|
||||
foreach (var statusEffect in periodicEffect.StatusEffects)
|
||||
{
|
||||
foreach (var sound in statusEffect.Sounds)
|
||||
{
|
||||
if (sound.Sound == null) { Submarine.ReloadRoundSound(sound); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "AfflictionPrefab (" + Name + ")";
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace Barotrauma
|
||||
private Affliction oxygenLowAffliction;
|
||||
private Affliction pressureAffliction;
|
||||
private Affliction stunAffliction;
|
||||
public Affliction BloodlossAffliction { get => bloodlossAffliction; }
|
||||
|
||||
public bool IsUnconscious
|
||||
{
|
||||
@@ -181,7 +182,7 @@ namespace Barotrauma
|
||||
public float BloodlossAmount
|
||||
{
|
||||
get { return bloodlossAffliction.Strength; }
|
||||
set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0, bloodlossAffliction.Prefab.MaxStrength); }
|
||||
}
|
||||
|
||||
public float Stun
|
||||
@@ -324,7 +325,11 @@ namespace Barotrauma
|
||||
if (kvp.Key == affliction)
|
||||
{
|
||||
int limbHealthIndex = limbHealths.IndexOf(kvp.Value);
|
||||
return Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealthIndex);
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.HealthIndex == limbHealthIndex) { return limb; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -658,7 +663,7 @@ namespace Barotrauma
|
||||
newStrength = Math.Min(existingAffliction.Prefab.MaxStrength, newStrength);
|
||||
if (existingAffliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
|
||||
existingAffliction.Strength = newStrength;
|
||||
existingAffliction.Source = newAffliction.Source;
|
||||
if (newAffliction.Source != null) { existingAffliction.Source = newAffliction.Source; }
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
@@ -744,7 +749,6 @@ namespace Barotrauma
|
||||
|
||||
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed));
|
||||
|
||||
// maybe a bit of a hacky way to do this. should inquire if there is a better way. M61T
|
||||
if (Character.InWater)
|
||||
{
|
||||
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed));
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma
|
||||
|
||||
public Skill PrimarySkill { get; }
|
||||
|
||||
public Job(JobPrefab jobPrefab, int variant = 0)
|
||||
public Job(JobPrefab jobPrefab, Rand.RandSync randSync = Rand.RandSync.Unsynced, int variant = 0)
|
||||
{
|
||||
prefab = jobPrefab;
|
||||
Variant = variant;
|
||||
@@ -43,7 +43,7 @@ namespace Barotrauma
|
||||
skills = new Dictionary<string, Skill>();
|
||||
foreach (SkillPrefab skillPrefab in prefab.Skills)
|
||||
{
|
||||
var skill = new Skill(skillPrefab);
|
||||
var skill = new Skill(skillPrefab, randSync);
|
||||
skills.Add(skillPrefab.Identifier, skill);
|
||||
if (skillPrefab.IsPrimarySkill) { PrimarySkill = skill; }
|
||||
}
|
||||
@@ -79,7 +79,7 @@ namespace Barotrauma
|
||||
{
|
||||
var prefab = JobPrefab.Random(randSync);
|
||||
var variant = Rand.Range(0, prefab.Variants, randSync);
|
||||
return new Job(prefab, variant);
|
||||
return new Job(prefab, randSync, variant);
|
||||
}
|
||||
|
||||
public float GetSkillLevel(string skillIdentifier)
|
||||
|
||||
@@ -36,10 +36,10 @@ namespace Barotrauma
|
||||
|
||||
public readonly float PriceMultiplier = 1.0f;
|
||||
|
||||
public Skill(SkillPrefab prefab)
|
||||
public Skill(SkillPrefab prefab, Rand.RandSync randSync)
|
||||
{
|
||||
Identifier = prefab.Identifier;
|
||||
level = Rand.Range(prefab.LevelRange.Start, prefab.LevelRange.End, Rand.RandSync.Server);
|
||||
level = Rand.Range(prefab.LevelRange.Start, prefab.LevelRange.End, randSync);
|
||||
icon = GetIcon();
|
||||
PriceMultiplier = prefab.PriceMultiplier;
|
||||
}
|
||||
|
||||
@@ -583,6 +583,8 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
public IEnumerable<StatusEffect> StatusEffects { get { return statusEffects; } }
|
||||
|
||||
public Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
|
||||
{
|
||||
this.ragdoll = ragdoll;
|
||||
@@ -756,8 +758,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (attacker != null)
|
||||
{
|
||||
var abilityAffliction = new AbilityAfflictionCharacter(newAffliction, character);
|
||||
attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction);
|
||||
var abilityAfflictionCharacter = new AbilityAfflictionCharacter(newAffliction, character);
|
||||
attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAfflictionCharacter);
|
||||
}
|
||||
if (applyAffliction)
|
||||
{
|
||||
@@ -1309,4 +1311,16 @@ namespace Barotrauma
|
||||
|
||||
partial void LoadParamsProjSpecific();
|
||||
}
|
||||
|
||||
class AbilityAfflictionCharacter : AbilityObject, IAbilityAffliction, IAbilityCharacter
|
||||
{
|
||||
public AbilityAfflictionCharacter(Affliction affliction, Character character)
|
||||
{
|
||||
Affliction = affliction;
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ namespace Barotrauma
|
||||
[Serialize(0f, true, description: "Width of the collider."), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float Width { get; set; }
|
||||
|
||||
[Serialize(10f, true, description: "The more the density the heavier the limb is."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
[Serialize(10f, true, description: "The more the density the heavier the limb is."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
public float Density { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
|
||||
+38
-21
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -7,15 +8,19 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAttackData : AbilityConditionData
|
||||
{
|
||||
[Flags]
|
||||
private enum WeaponType
|
||||
{
|
||||
Any = 0,
|
||||
Melee = 1,
|
||||
Ranged = 2,
|
||||
HandheldRanged = 3,
|
||||
Turret = 4
|
||||
HandheldRanged = 4,
|
||||
Turret = 8,
|
||||
NoWeapon = 16
|
||||
};
|
||||
|
||||
private static readonly List<WeaponType> WeaponTypeValues = Enum.GetValues(typeof(WeaponType)).Cast<WeaponType>().ToList();
|
||||
|
||||
private readonly string itemIdentifier;
|
||||
private readonly string[] tags;
|
||||
private readonly WeaponType weapontype;
|
||||
@@ -65,27 +70,39 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (weapontype != WeaponType.Any)
|
||||
{
|
||||
switch (weapontype)
|
||||
foreach (WeaponType wt in WeaponTypeValues)
|
||||
{
|
||||
// it is possible that an item that has both a melee and a projectile component will return true
|
||||
// even when not used as a melee/ranged weapon respectively
|
||||
// attackdata should contain data regarding whether the attack is melee or not
|
||||
case WeaponType.Melee:
|
||||
return item?.GetComponent<MeleeWeapon>() != null;
|
||||
case WeaponType.Ranged:
|
||||
return item?.GetComponent<Projectile>() != null;
|
||||
case WeaponType.HandheldRanged:
|
||||
{
|
||||
var projectile = item?.GetComponent<Projectile>();
|
||||
return projectile?.Launcher?.GetComponent<Holdable>() != null;
|
||||
}
|
||||
case WeaponType.Turret:
|
||||
{
|
||||
var projectile = item?.GetComponent<Projectile>();
|
||||
return projectile?.Launcher?.GetComponent<Turret>() != null;
|
||||
}
|
||||
if (wt == WeaponType.Any || !weapontype.HasFlag(wt)) { continue; }
|
||||
switch (wt)
|
||||
{
|
||||
// it is possible that an item that has both a melee and a projectile component will return true
|
||||
// even when not used as a melee/ranged weapon respectively
|
||||
// attackdata should contain data regarding whether the attack is melee or not
|
||||
case WeaponType.Melee:
|
||||
if (item?.GetComponent<MeleeWeapon>() != null) { return true; }
|
||||
break;
|
||||
case WeaponType.Ranged:
|
||||
if (item?.GetComponent<Projectile>() != null) { return true; }
|
||||
break;
|
||||
case WeaponType.HandheldRanged:
|
||||
{
|
||||
var projectile = item?.GetComponent<Projectile>();
|
||||
if (projectile?.Launcher?.GetComponent<Holdable>() != null) { return true; }
|
||||
}
|
||||
break;
|
||||
case WeaponType.Turret:
|
||||
{
|
||||
var projectile = item?.GetComponent<Projectile>();
|
||||
if (projectile?.Launcher?.GetComponent<Turret>() != null) { return true; }
|
||||
}
|
||||
break;
|
||||
case WeaponType.NoWeapon:
|
||||
if (item == null) { return true; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItemInSubmarine : AbilityConditionData
|
||||
{
|
||||
private readonly SubmarineType? submarineType;
|
||||
|
||||
public AbilityConditionItemInSubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
if (conditionElement.Attribute("submarinetype") != null)
|
||||
{
|
||||
submarineType = conditionElement.GetAttributeEnum<SubmarineType>("submarinetype", SubmarineType.Player);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (submarineType.HasValue)
|
||||
{
|
||||
return item.Submarine.Info?.Type == submarineType.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItem));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItemOutsideSubmarine : AbilityConditionData
|
||||
{
|
||||
|
||||
public AbilityConditionItemOutsideSubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
return item.Submarine == null || item.Submarine.TeamID != character.Info.TeamID;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItem));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItemWreck : AbilityConditionData
|
||||
{
|
||||
|
||||
public AbilityConditionItemWreck(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
return item.Submarine?.Info?.IsWreck ?? false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityItem));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -18,13 +18,13 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityString)?.String is string skillIdentifier)
|
||||
if ((abilityObject as IAbilitySkillIdentifier)?.SkillIdentifier is string skillIdentifier)
|
||||
{
|
||||
return MatchesConditionSpecific(skillIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityString));
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilitySkillIdentifier));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -30,9 +30,9 @@
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityString
|
||||
interface IAbilitySkillIdentifier
|
||||
{
|
||||
public string String { get; set; }
|
||||
public string SkillIdentifier { get; set; }
|
||||
}
|
||||
|
||||
interface IAbilityAffliction
|
||||
|
||||
-169
@@ -16,173 +16,4 @@ namespace Barotrauma.Abilities
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
class AbilityItem : AbilityObject, IAbilityItem
|
||||
{
|
||||
public AbilityItem(Item item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
public Item Item { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValue : AbilityObject, IAbilityValue
|
||||
{
|
||||
public AbilityValue(float value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
}
|
||||
|
||||
class AbilityAffliction : AbilityObject, IAbilityAffliction
|
||||
{
|
||||
public AbilityAffliction(Affliction affliction)
|
||||
{
|
||||
Affliction = affliction;
|
||||
}
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
class AbilityAfflictionCharacter : AbilityObject, IAbilityAffliction, IAbilityCharacter
|
||||
{
|
||||
public AbilityAfflictionCharacter(Affliction affliction, Character character)
|
||||
{
|
||||
Affliction = affliction;
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityValueItem(float value, ItemPrefab itemPrefab)
|
||||
{
|
||||
Value = value;
|
||||
ItemPrefab = itemPrefab;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
|
||||
class AbilityItemPrefabItem : AbilityObject, IAbilityItem, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityItemPrefabItem(Item item, ItemPrefab itemPrefab)
|
||||
{
|
||||
Item = item;
|
||||
ItemPrefab = itemPrefab;
|
||||
}
|
||||
public Item Item { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString
|
||||
{
|
||||
public AbilityValueString(float value, string abilityString)
|
||||
{
|
||||
Value = value;
|
||||
String = abilityString;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public string String { get; set; }
|
||||
}
|
||||
|
||||
class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString
|
||||
{
|
||||
public AbilityStringCharacter(string abilityString, Character character)
|
||||
{
|
||||
String = abilityString;
|
||||
Character = character;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public string String { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueAffliction : AbilityObject, IAbilityValue, IAbilityAffliction
|
||||
{
|
||||
public AbilityValueAffliction(float value, Affliction affliction)
|
||||
{
|
||||
Value = value;
|
||||
Affliction = affliction;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public Affliction Affliction { get; set; }
|
||||
}
|
||||
|
||||
class AbilityValueMission : AbilityObject, IAbilityValue, IAbilityMission
|
||||
{
|
||||
public AbilityValueMission(float value, Mission mission)
|
||||
{
|
||||
Value = value;
|
||||
Mission = mission;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
{
|
||||
public AbilityLocation(Location location)
|
||||
{
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public Location Location { get; set; }
|
||||
}
|
||||
|
||||
// this is an exception class that should only be passed in this form, so classes that use it should cast into it directly
|
||||
class AbilityAttackData : AbilityObject, IAbilityCharacter
|
||||
{
|
||||
public float DamageMultiplier { get; set; } = 1f;
|
||||
public float AddedPenetration { get; set; } = 0f;
|
||||
public List<Affliction> Afflictions { get; set; }
|
||||
public bool ShouldImplode { get; set; } = false;
|
||||
public Attack SourceAttack { get; }
|
||||
public Character Character { get; set; }
|
||||
public Character Attacker { get; set; }
|
||||
|
||||
public AbilityAttackData(Attack sourceAttack, Character character)
|
||||
{
|
||||
SourceAttack = sourceAttack;
|
||||
Character = character;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityApplyTreatment : AbilityObject, IAbilityCharacter, IAbilityItem
|
||||
{
|
||||
public Character Character { get; set; }
|
||||
|
||||
public Character User { get; set; }
|
||||
|
||||
public Item Item { get; set; }
|
||||
|
||||
public AbilityApplyTreatment(Character user, Character target, Item item)
|
||||
{
|
||||
Character = target;
|
||||
User = user;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityAttackResult : AbilityObject, IAbilityAttackResult
|
||||
{
|
||||
public AttackResult AttackResult { get; set; }
|
||||
|
||||
public AbilityAttackResult(AttackResult attackResult)
|
||||
{
|
||||
AttackResult = attackResult;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityCharacterSubmarine : AbilityObject, IAbilityCharacter, IAbilitySubmarine
|
||||
{
|
||||
public AbilityCharacterSubmarine(Character character, Submarine submarine)
|
||||
{
|
||||
Character = character;
|
||||
Submarine = submarine;
|
||||
}
|
||||
public Character Character { get; set; }
|
||||
public Submarine Submarine { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ namespace Barotrauma.Abilities
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}");
|
||||
}
|
||||
|
||||
protected void LogabilityObjectMismatch()
|
||||
protected void LogAbilityObjectMismatch()
|
||||
{
|
||||
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}");
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
LogAbilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
LogAbilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
LogAbilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
LogAbilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
else
|
||||
{
|
||||
LogabilityObjectMismatch();
|
||||
LogAbilityObjectMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,6 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
@@ -18,7 +16,7 @@ namespace Barotrauma.Abilities
|
||||
if (abilityObject is AbilitySkillGain abilitySkillGain && abilitySkillGain.Character != Character)
|
||||
{
|
||||
if (ignoreAbilitySkillGain && abilitySkillGain.GainedFromAbility) { return; }
|
||||
Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, gainedFromAbility: true);
|
||||
Character.Info?.IncreaseSkillLevel(abilitySkillGain.SkillIdentifier, 1.0f, gainedFromAbility: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityString)?.String is string skillIdentifier)
|
||||
if ((abilityObject as IAbilitySkillIdentifier)?.SkillIdentifier is string skillIdentifier)
|
||||
{
|
||||
if (skillIdentifier != lastSkillIdentifier)
|
||||
{
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ namespace Barotrauma.Abilities
|
||||
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
|
||||
{
|
||||
// this should just be its own class, misleading to inherit here
|
||||
private string tag;
|
||||
private readonly string tag;
|
||||
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
tag = abilityElement.GetAttributeString("tag", "");
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Abilities
|
||||
if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
|
||||
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
float closestDistance = squaredMaxDistance;
|
||||
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user