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))
|
||||
{
|
||||
|
||||
@@ -984,8 +984,8 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("teleportsub", "teleportsub [start/end/cursor]: Teleport the submarine to the position of the cursor, or the start or end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.", (string[] args) =>
|
||||
{
|
||||
if (Submarine.MainSub == null || Level.Loaded == null) return;
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
if (Submarine.MainSub == null) { return; }
|
||||
if (Level.Loaded?.Type == LevelData.LevelType.Outpost && GameMain.GameSession != null)
|
||||
{
|
||||
NewMessage("The teleportsub command is unavailable in outpost levels!", Color.Red);
|
||||
return;
|
||||
@@ -1001,6 +1001,11 @@ namespace Barotrauma
|
||||
}
|
||||
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Level.Loaded == null)
|
||||
{
|
||||
NewMessage("Can't teleport the sub to the start of the level (no level loaded).", Color.Red);
|
||||
return;
|
||||
}
|
||||
Vector2 pos = Level.Loaded.StartPosition;
|
||||
if (Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
@@ -1010,6 +1015,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Level.Loaded == null)
|
||||
{
|
||||
NewMessage("Can't teleport the sub to the end of the level (no level loaded).", Color.Red);
|
||||
return;
|
||||
}
|
||||
Vector2 pos = Level.Loaded.EndPosition;
|
||||
if (Level.Loaded.EndOutpost != null)
|
||||
{
|
||||
@@ -1189,6 +1199,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.GetComponent<GeneticMaterial>() != null) { continue; }
|
||||
it.Condition = it.MaxCondition;
|
||||
}
|
||||
}, null, true));
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace Barotrauma
|
||||
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters();
|
||||
|
||||
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
|
||||
var experienceGainMultiplier = new AbilityValue(1f);
|
||||
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
|
||||
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
|
||||
|
||||
@@ -374,11 +374,11 @@ namespace Barotrauma
|
||||
#endif
|
||||
|
||||
// apply money gains afterwards to prevent them from affecting XP gains
|
||||
var moneyGainMission = new AbilityValueMission(1f, this);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, moneyGainMission));
|
||||
crewCharacters.ForEach(c => moneyGainMission.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
var missionMoneyGainMultiplier = new AbilityMissionMoneyGainMultiplier(this, 1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
|
||||
crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
|
||||
campaign.Money += (int)(reward * moneyGainMission.Value);
|
||||
campaign.Money += (int)(reward * missionMoneyGainMultiplier.Value);
|
||||
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
@@ -534,4 +534,16 @@ namespace Barotrauma
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityMissionMoneyGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
|
||||
{
|
||||
public AbilityMissionMoneyGainMultiplier(Mission mission, float moneyGainMultiplier)
|
||||
{
|
||||
Value = moneyGainMultiplier;
|
||||
Mission = mission;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly float scatter;
|
||||
private readonly float offset;
|
||||
private readonly float delayBetweenSpawns;
|
||||
|
||||
private Vector2? spawnPos;
|
||||
|
||||
@@ -92,6 +93,7 @@ namespace Barotrauma
|
||||
|
||||
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
|
||||
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000);
|
||||
delayBetweenSpawns = prefab.ConfigElement.GetAttributeFloat("delaybetweenspawns", 0.1f);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
@@ -538,7 +540,7 @@ namespace Barotrauma
|
||||
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
|
||||
DebugConsole.NewMessage($"Spawned: {ToString()}. Strength: {StringFormatter.FormatZeroDecimal(monsters.Sum(m => m.Params.AI.CombatStrength))}.", Color.LightBlue, debugOnly: true);
|
||||
}
|
||||
}, Rand.Range(0f, amount / 2f));
|
||||
}, delayBetweenSpawns * i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace Barotrauma
|
||||
|
||||
requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null);
|
||||
RequireBeaconStation = prefab.ConfigElement.GetAttributeBool("requirebeaconstation", false);
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Start");
|
||||
}
|
||||
|
||||
public void AddTarget(string tag, Entity target)
|
||||
@@ -229,5 +231,11 @@ namespace Barotrauma
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Finished()
|
||||
{
|
||||
base.Finished();
|
||||
GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Finished:{CurrentActionIndex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Barotrauma.Extensions
|
||||
return source.Count(predicate) > 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<T> ToEnumerable<T>(this T item)
|
||||
{
|
||||
yield return item;
|
||||
@@ -196,5 +196,28 @@ namespace Barotrauma.Extensions
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as FirstOrDefault but will always return null instead of default(T) when no element is found
|
||||
/// </summary>
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> source, Func<T, bool> predicate) where T : struct
|
||||
{
|
||||
if (source.FirstOrDefault(predicate) is var first && !first.Equals(default(T)))
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> source) where T : struct
|
||||
{
|
||||
if (source.FirstOrDefault() is var first && !first.Equals(default(T)))
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,13 @@ namespace Barotrauma
|
||||
SetConsent(Consent.Error);
|
||||
}
|
||||
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
DebugConsole.AddWarning("Error in GameAnalyticsManager.GetConsent: Could not get a Steam authentication ticket (not connected to Steam).");
|
||||
SetConsent(Consent.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
string authTicketStr;
|
||||
try
|
||||
{
|
||||
@@ -183,7 +190,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
var response = ((Task<IRestResponse>)t).Result;
|
||||
if (!t.TryGetResult(out IRestResponse response)) { return; }
|
||||
if (!CheckResponse(response))
|
||||
{
|
||||
SetConsent(Consent.Error);
|
||||
|
||||
@@ -367,7 +367,6 @@ namespace Barotrauma
|
||||
+ GameMain.Version.ToString()
|
||||
+ exeName + ":"
|
||||
+ ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash) + ":"
|
||||
+ AssemblyInfo.GitBranch + ":"
|
||||
+ AssemblyInfo.GitRevision + ":"
|
||||
+ buildConfiguration);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (order.TargetEntity == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to add an order with no target entity to CrewManager!\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
string message = $"Attempted to add a \"{order.Name}\" order with no target entity to CrewManager!\n{Environment.StackTrace.CleanupStackTrace()}";
|
||||
DebugConsole.AddWarning(message);
|
||||
GameAnalyticsManager.AddErrorEventOnce("CrewManager.AddOrder:OrderTargetEntityNull", GameAnalyticsManager.ErrorSeverity.Error, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -185,6 +187,10 @@ namespace Barotrauma
|
||||
|
||||
public void InitRound()
|
||||
{
|
||||
#if CLIENT
|
||||
GUIContextMenu.CurrentContextMenu = null;
|
||||
#endif
|
||||
|
||||
characters.Clear();
|
||||
|
||||
List<WayPoint> spawnWaypoints = null;
|
||||
@@ -437,17 +443,17 @@ namespace Barotrauma
|
||||
return filteredCharacters
|
||||
// 1. Prioritize those who are on the same submarine than the controlled character
|
||||
.OrderByDescending(c => Character.Controlled == null || c.Submarine == Character.Controlled.Submarine)
|
||||
// 2. Prioritize those who have been given the same maintenance or operate order as now issued
|
||||
.ThenByDescending(c => c.CurrentOrders.Any(o =>
|
||||
o.Order != null && o.Order.Identifier == order.Identifier &&
|
||||
(order.Category == OrderCategory.Maintenance || order.Category == OrderCategory.Operate)))
|
||||
// 2. Prioritize those who are already ordered to operate the device
|
||||
.ThenByDescending(c => order.Category == OrderCategory.Operate && c.CurrentOrders.Any(o => o.Order != null && o.Order.Identifier == order.Identifier && o.Order.TargetEntity == order.TargetEntity))
|
||||
// 3. Prioritize those with the appropriate job for the order
|
||||
.ThenByDescending(c => order.HasAppropriateJob(c))
|
||||
// 4. Prioritize bots over player controlled characters
|
||||
// 4. Prioritize those who don't yet have another Operate order of the same kind (which allows quick-assigning multiple Operate orders to different characters)
|
||||
.ThenByDescending(c => order.Category == OrderCategory.Operate && c.CurrentOrders.None(o => o.Order != null && o.Order.Identifier == order.Identifier))
|
||||
// 5. Prioritize bots over player controlled characters
|
||||
.ThenByDescending(c => c.IsBot)
|
||||
// 5. Use the priority value of the current objective
|
||||
// 6. Use the priority value of the current objective
|
||||
.ThenBy(c => c.AIController is HumanAIController humanAI ? humanAI.ObjectiveManager.CurrentObjective?.Priority : 0)
|
||||
// 6. Prioritize those with the best skill for the order
|
||||
// 7. Prioritize those with the best skill for the order
|
||||
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,11 @@ namespace Barotrauma
|
||||
//there can be no events before this time has passed during the 1st campaign round
|
||||
const float FirstRoundEventDelay = 0.0f;
|
||||
|
||||
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub }
|
||||
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic }
|
||||
|
||||
public readonly CargoManager CargoManager;
|
||||
public UpgradeManager UpgradeManager;
|
||||
public MedicalClinic MedicalClinic;
|
||||
|
||||
public List<Faction> Factions;
|
||||
|
||||
@@ -176,6 +177,7 @@ namespace Barotrauma
|
||||
{
|
||||
Money = InitialMoney;
|
||||
CargoManager = new CargoManager(this);
|
||||
MedicalClinic = new MedicalClinic(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -192,6 +192,37 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public static List<SubmarineInfo> GetCampaignSubs()
|
||||
{
|
||||
bool isSubmarineVisible(SubmarineInfo s)
|
||||
=> !GameMain.NetworkMember.ServerSettings.HiddenSubs.Any(h
|
||||
=> s.Name.Equals(h, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
List<SubmarineInfo> availableSubs =
|
||||
SubmarineInfo.SavedSubmarines
|
||||
.Where(s =>
|
||||
s.IsCampaignCompatible
|
||||
&& isSubmarineVisible(s))
|
||||
.ToList();
|
||||
|
||||
if (!availableSubs.Any())
|
||||
{
|
||||
//None of the available subs were marked as campaign-compatible, just include all visible subs
|
||||
availableSubs.AddRange(
|
||||
SubmarineInfo.SavedSubmarines
|
||||
.Where(isSubmarineVisible));
|
||||
}
|
||||
|
||||
if (!availableSubs.Any())
|
||||
{
|
||||
//No subs are visible at all! Just make the selected one available
|
||||
availableSubs.Add(GameMain.NetLobbyScreen.SelectedSub);
|
||||
}
|
||||
|
||||
return availableSubs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,9 +411,9 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.ProgressionStatus.Start,
|
||||
GameMode?.Name ?? "none");
|
||||
|
||||
string eventId = "StartRound:GameMode:" + (GameMode?.Name ?? "none") + ":";
|
||||
string eventId = "StartRound:" + (GameMode?.Preset?.Identifier ?? "none") + ":";
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"));
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name ?? "none"));
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Preset?.Identifier ?? "none"));
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0));
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
@@ -421,6 +421,17 @@ namespace Barotrauma
|
||||
}
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none"));
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"));
|
||||
if (GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
if (campaignMode.Map?.Radiation != null && campaignMode.Map.Radiation.Enabled)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "RadiationEnabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "RadiationDisabled");
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (GameMode is CampaignMode) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); }
|
||||
@@ -457,6 +468,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
ReadyCheck.ReadyCheckCooldown = DateTime.MinValue;
|
||||
|
||||
GUI.PreventPauseMenuToggle = false;
|
||||
|
||||
HintManager.OnRoundStarted();
|
||||
@@ -895,14 +908,7 @@ namespace Barotrauma
|
||||
|
||||
((CampaignMode)GameMode).Save(doc.Root);
|
||||
|
||||
try
|
||||
{
|
||||
doc.SaveSafe(filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving gamesession to \"" + filePath + "\" failed!", e);
|
||||
}
|
||||
doc.SaveSafe(filePath, throwExceptions: true);
|
||||
}
|
||||
|
||||
/*public void Load(XElement saveElement)
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal partial class MedicalClinic
|
||||
{
|
||||
public enum NetworkHeader
|
||||
{
|
||||
REQUEST_AFFLICTIONS,
|
||||
REQUEST_PENDING,
|
||||
ADD_PENDING,
|
||||
REMOVE_PENDING,
|
||||
CLEAR_PENDING,
|
||||
HEAL_PENDING
|
||||
}
|
||||
|
||||
public enum AfflictionSeverity
|
||||
{
|
||||
Low,
|
||||
Medium,
|
||||
High
|
||||
}
|
||||
|
||||
public enum MessageFlag
|
||||
{
|
||||
Response, // responding to your request
|
||||
Announce // responding to someone else's request
|
||||
}
|
||||
|
||||
public enum HealRequestResult
|
||||
{
|
||||
Unknown, // everything is not ok
|
||||
Success, // everything ok
|
||||
InsufficientFunds, // not enough money
|
||||
Refused // the outpost has refused to provide medical assistance
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public struct NetHealRequest : INetSerializableStruct
|
||||
{
|
||||
public HealRequestResult Result;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public struct NetRemovedAffliction : INetSerializableStruct
|
||||
{
|
||||
public NetCrewMember CrewMember;
|
||||
public NetAffliction Affliction;
|
||||
}
|
||||
|
||||
public struct NetPendingCrew : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize(ArrayMaxSize = CrewManager.MaxCrewSize)]
|
||||
public NetCrewMember[] CrewMembers;
|
||||
}
|
||||
|
||||
public struct NetAffliction : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public string Identifier;
|
||||
|
||||
[NetworkSerialize]
|
||||
public ushort Strength;
|
||||
|
||||
[NetworkSerialize]
|
||||
public ushort Price;
|
||||
|
||||
public AfflictionSeverity AfflictionSeverity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Prefab is null) { return AfflictionSeverity.Low; }
|
||||
|
||||
float normalizedStrength = Strength / Prefab.MaxStrength;
|
||||
|
||||
// lesser than 0.1
|
||||
if (normalizedStrength <= 0.1)
|
||||
{
|
||||
return AfflictionSeverity.Low;
|
||||
}
|
||||
|
||||
// between 0.1 and 0.5
|
||||
if (normalizedStrength > 0.1f && normalizedStrength < 0.5f)
|
||||
{
|
||||
return AfflictionSeverity.Medium;
|
||||
}
|
||||
|
||||
// greater than 0.5
|
||||
return AfflictionSeverity.High;
|
||||
}
|
||||
}
|
||||
|
||||
public Affliction Affliction
|
||||
{
|
||||
set
|
||||
{
|
||||
Identifier = value.Identifier;
|
||||
Strength = (ushort)Math.Ceiling(value.Strength);
|
||||
Price = (ushort)(Strength * value.Prefab.HealCostMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
private AfflictionPrefab? cachedPrefab;
|
||||
|
||||
public AfflictionPrefab? Prefab
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedPrefab is { } cached) { return cached; }
|
||||
|
||||
foreach (AfflictionPrefab prefab in AfflictionPrefab.List)
|
||||
{
|
||||
if (prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cachedPrefab = prefab;
|
||||
return prefab;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
cachedPrefab = value;
|
||||
Identifier = value?.Identifier ?? string.Empty;
|
||||
Strength = 0;
|
||||
Price = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool AfflictionEquals(AfflictionPrefab prefab)
|
||||
{
|
||||
return prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public readonly bool AfflictionEquals(NetAffliction affliction)
|
||||
{
|
||||
return affliction.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public struct NetCrewMember : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public int CharacterInfoID;
|
||||
|
||||
[NetworkSerialize]
|
||||
public NetAffliction[] Afflictions;
|
||||
|
||||
public CharacterInfo CharacterInfo
|
||||
{
|
||||
set => CharacterInfoID = value.GetIdentifierUsingOriginalName();
|
||||
}
|
||||
|
||||
public readonly CharacterInfo? FindCharacterInfo(ImmutableArray<CharacterInfo> crew)
|
||||
{
|
||||
foreach (CharacterInfo info in crew)
|
||||
{
|
||||
if (info.GetIdentifierUsingOriginalName() == CharacterInfoID)
|
||||
{
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public readonly bool CharacterEquals(NetCrewMember crewMember)
|
||||
{
|
||||
return crewMember.CharacterInfoID == CharacterInfoID;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CampaignMode? campaign;
|
||||
|
||||
public MedicalClinic(CampaignMode campaign)
|
||||
{
|
||||
this.campaign = campaign;
|
||||
}
|
||||
|
||||
public readonly List<NetCrewMember> PendingHeals = new List<NetCrewMember>();
|
||||
|
||||
public Action? OnUpdate;
|
||||
|
||||
private static bool IsOutpostInCombat()
|
||||
{
|
||||
if (!(Level.Loaded is { Type: LevelData.LevelType.Outpost })) { return false; }
|
||||
|
||||
IEnumerable<Character> crew = GetCrewCharacters().Where(c => c.Character != null).Select(c => c.Character).ToImmutableHashSet();
|
||||
|
||||
foreach (Character npc in Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
{
|
||||
bool isInCombatWithCrew = !npc.IsInstigator && npc.AIController is HumanAIController { ObjectiveManager: { CurrentObjective: AIObjectiveCombat combatObjective } } && crew.Contains(combatObjective.Enemy);
|
||||
if (isInCombatWithCrew) { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private HealRequestResult HealAllPending(bool force = false)
|
||||
{
|
||||
int totalCost = GetTotalCost();
|
||||
if (!force)
|
||||
{
|
||||
if (GetMoney() < totalCost) { return HealRequestResult.InsufficientFunds; }
|
||||
|
||||
if (IsOutpostInCombat()) { return HealRequestResult.Refused; }
|
||||
}
|
||||
|
||||
ImmutableArray<CharacterInfo> crew = GetCrewCharacters();
|
||||
foreach (NetCrewMember crewMember in PendingHeals)
|
||||
{
|
||||
CharacterInfo? targetCharacter = crewMember.FindCharacterInfo(crew);
|
||||
if (!(targetCharacter?.Character is { CharacterHealth: { } health })) { continue; }
|
||||
|
||||
foreach (NetAffliction affliction in crewMember.Afflictions)
|
||||
{
|
||||
health.ReduceAffliction(null, affliction.Identifier, affliction.Prefab?.MaxStrength ?? affliction.Strength);
|
||||
}
|
||||
}
|
||||
|
||||
if (campaign != null)
|
||||
{
|
||||
campaign.Money -= totalCost;
|
||||
}
|
||||
|
||||
ClearPendingHeals();
|
||||
|
||||
return HealRequestResult.Success;
|
||||
}
|
||||
|
||||
private void ClearPendingHeals()
|
||||
{
|
||||
PendingHeals.Clear();
|
||||
}
|
||||
|
||||
private void RemovePendingAffliction(NetCrewMember crewMember, NetAffliction affliction)
|
||||
{
|
||||
foreach (NetCrewMember listMember in PendingHeals.ToList())
|
||||
{
|
||||
PendingHeals.Remove(listMember);
|
||||
NetCrewMember pendingMember = listMember;
|
||||
|
||||
if (pendingMember.CharacterEquals(crewMember))
|
||||
{
|
||||
List<NetAffliction> newAfflictions = new List<NetAffliction>();
|
||||
foreach (NetAffliction pendingAffliction in pendingMember.Afflictions)
|
||||
{
|
||||
if (pendingAffliction.AfflictionEquals(affliction)) { continue; }
|
||||
|
||||
newAfflictions.Add(pendingAffliction);
|
||||
}
|
||||
|
||||
pendingMember.Afflictions = newAfflictions.ToArray();
|
||||
}
|
||||
|
||||
if (!pendingMember.Afflictions.Any()) { continue; }
|
||||
|
||||
PendingHeals.Add(pendingMember);
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertPendingCrewMember(NetCrewMember crewMember)
|
||||
{
|
||||
if (PendingHeals.FirstOrNull(m => m.CharacterEquals(crewMember)) is { } foundHeal)
|
||||
{
|
||||
PendingHeals.Remove(foundHeal);
|
||||
}
|
||||
|
||||
PendingHeals.Add(crewMember);
|
||||
}
|
||||
|
||||
private NetAffliction[] GetAllAfflictions(CharacterHealth health)
|
||||
{
|
||||
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(a => !a.Prefab.IsBuff && a.Strength > GetShowTreshold(a));
|
||||
|
||||
List<NetAffliction> afflictions = new List<NetAffliction>();
|
||||
|
||||
foreach (Affliction affliction in rawAfflictions)
|
||||
{
|
||||
NetAffliction newAffliction;
|
||||
if (afflictions.FirstOrNull(netAffliction => netAffliction.AfflictionEquals(affliction.Prefab)) is { } foundAffliction)
|
||||
{
|
||||
afflictions.Remove(foundAffliction);
|
||||
foundAffliction.Strength += (ushort)affliction.Strength;
|
||||
foundAffliction.Price += (ushort)GetAdjustedPrice((int)(affliction.Prefab.HealCostMultiplier * affliction.Strength));
|
||||
newAffliction = foundAffliction;
|
||||
}
|
||||
else
|
||||
{
|
||||
newAffliction = new NetAffliction { Affliction = affliction };
|
||||
newAffliction.Price = (ushort)GetAdjustedPrice(newAffliction.Price);
|
||||
}
|
||||
|
||||
afflictions.Add(newAffliction);
|
||||
}
|
||||
|
||||
return afflictions.ToArray();
|
||||
|
||||
static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
|
||||
}
|
||||
|
||||
public int GetTotalCost() => PendingHeals.SelectMany(h => h.Afflictions).Aggregate(0, (current, affliction) => current + affliction.Price);
|
||||
|
||||
private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue;
|
||||
|
||||
public int GetMoney() => campaign?.Money ?? 0;
|
||||
|
||||
public static ImmutableArray<CharacterInfo> GetCrewCharacters()
|
||||
{
|
||||
#if DEBUG && CLIENT
|
||||
if (Screen.Selected is TestScreen)
|
||||
{
|
||||
return TestInfos.ToImmutableArray();
|
||||
}
|
||||
#endif
|
||||
|
||||
return Character.CharacterList.Where(c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(c => c.Info).ToImmutableArray();
|
||||
}
|
||||
|
||||
#if DEBUG && CLIENT
|
||||
private static readonly CharacterInfo[] TestInfos =
|
||||
{
|
||||
new CharacterInfo("human"),
|
||||
new CharacterInfo("human"),
|
||||
new CharacterInfo("human"),
|
||||
new CharacterInfo("human"),
|
||||
new CharacterInfo("human"),
|
||||
new CharacterInfo("human"),
|
||||
new CharacterInfo("human")
|
||||
};
|
||||
|
||||
private static readonly NetAffliction[] TestAfflictions =
|
||||
{
|
||||
new NetAffliction { Identifier = "internaldamage", Strength = 80, Price = 10 },
|
||||
new NetAffliction { Identifier = "blunttrauma", Strength = 50, Price = 10 },
|
||||
new NetAffliction { Identifier = "lacerations", Strength = 20, Price = 10 },
|
||||
new NetAffliction { Identifier = "burn", Strength = 10, Price = 10 }
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -144,16 +144,6 @@ namespace Barotrauma.Items.Components
|
||||
if (linkedGap == null)
|
||||
{
|
||||
Rectangle rect = item.Rect;
|
||||
if (IsHorizontal)
|
||||
{
|
||||
rect.Y += 5;
|
||||
rect.Height += 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.X -= 5;
|
||||
rect.Width += 10;
|
||||
}
|
||||
linkedGap = new Gap(rect, !IsHorizontal, Item.Submarine)
|
||||
{
|
||||
Submarine = item.Submarine
|
||||
|
||||
+20
-14
@@ -55,17 +55,22 @@ namespace Barotrauma.Items.Components
|
||||
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 1f, ValueStep = 1f, DecimalCount = 0), Serialize("1,3", true, "Minumum and maximum amount of items or creatures to spawn in one attempt")]
|
||||
public Vector2 SpawnAmountRange { get; set; }
|
||||
|
||||
[Editable(MinValueInt = int.MinValue, MaxValueInt = int.MaxValue), Serialize(8, true, "Amount of items or creatures in the spawn area that will prevent further items or creatures from being spawned")]
|
||||
[Editable(MinValueInt = 0, MaxValueInt = int.MaxValue), Serialize(8, true, "Total maximum amount of items or creatures that can be spawned. 0 = unrestricted.")]
|
||||
public int MaximumAmount { get; set; }
|
||||
|
||||
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize(500f, true, "Inflate the circle of rectangle by this value to extend the area that counts towards the maximum amount of items or enemies to be spawned")]
|
||||
[Editable(MinValueInt = 0, MaxValueInt = int.MaxValue), Serialize(8, true, "Amount of items or creatures in the spawn area that will prevent further items or creatures from being spawned. 0 = unrestricted.")]
|
||||
public int MaximumAmountInArea { get; set; }
|
||||
|
||||
[Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize(500f, true, "Inflate the circle of rectangle by this value to extend the area that counts towards the maximum amount of items or enemies to be spawned")]
|
||||
public float MaximumAmountRangePadding { get; set; }
|
||||
|
||||
[Serialize(true, true, "")]
|
||||
public bool CanSpawn { get; set; } = true;
|
||||
|
||||
private float SpawnTimer;
|
||||
private float? SpawnTimerGoal;
|
||||
private float spawnTimer;
|
||||
private float? spawnTimerGoal;
|
||||
|
||||
private int spawnedAmount = 0;
|
||||
|
||||
public EntitySpawnerComponent(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
@@ -115,15 +120,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (minTime < 0 && maxTime < 0) { return; }
|
||||
|
||||
SpawnTimerGoal ??= Rand.Range(minTime, maxTime, Rand.RandSync.Unsynced);
|
||||
spawnTimerGoal ??= Rand.Range(minTime, maxTime, Rand.RandSync.Unsynced);
|
||||
|
||||
SpawnTimer += deltaTime;
|
||||
spawnTimer += deltaTime;
|
||||
|
||||
if (SpawnTimer > SpawnTimerGoal)
|
||||
if (spawnTimer > spawnTimerGoal)
|
||||
{
|
||||
Spawn();
|
||||
SpawnTimerGoal = null;
|
||||
SpawnTimer = 0;
|
||||
spawnTimerGoal = null;
|
||||
spawnTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,12 +154,12 @@ namespace Barotrauma.Items.Components
|
||||
private RectangleF GetAreaRectangle(Vector2 size, Vector2 offset, bool draw)
|
||||
{
|
||||
Vector2 pos = item.WorldPosition;
|
||||
pos += offset;
|
||||
if (draw)
|
||||
{
|
||||
pos.Y = -pos.Y;
|
||||
}
|
||||
|
||||
pos += offset;
|
||||
RectangleF rect = new RectangleF(pos.X - size.X / 2f, pos.Y - size.Y / 2f, size.X, size.Y);
|
||||
return rect;
|
||||
}
|
||||
@@ -162,6 +167,7 @@ namespace Barotrauma.Items.Components
|
||||
private bool CanSpawnMore()
|
||||
{
|
||||
if (!CanSpawn) { return false; }
|
||||
if (MaximumAmount > 0 && spawnedAmount >= MaximumAmount) { return false; }
|
||||
|
||||
if (OnlySpawnWhenCrewInRange)
|
||||
{
|
||||
@@ -171,10 +177,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (MaximumAmount < 0) { return true; }
|
||||
if (MaximumAmountInArea <= 0) { return true; }
|
||||
|
||||
int amount;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(SpeciesName))
|
||||
{
|
||||
amount = Character.CharacterList.Count(c => !c.IsDead && c.SpeciesName.Equals(SpeciesName, StringComparison.OrdinalIgnoreCase) && IsInRange(c.WorldPosition, crewArea: false, rangePad: true));
|
||||
@@ -188,13 +193,12 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
return amount < MaximumAmount;
|
||||
return amount < MaximumAmountInArea;
|
||||
}
|
||||
|
||||
private bool IsInRange(Vector2 worldPos, bool crewArea = false, bool rangePad = false)
|
||||
{
|
||||
Vector2 offset = crewArea ? CrewAreaOffset : SpawnAreaOffset;
|
||||
offset.Y = -offset.Y;
|
||||
switch (crewArea ? CrewAreaShape : SpawnAreaShape)
|
||||
{
|
||||
case AreaShape.Circle:
|
||||
@@ -269,6 +273,7 @@ namespace Barotrauma.Items.Components
|
||||
string[] allSpecies = SpeciesName.Split(',');
|
||||
string species = allSpecies.GetRandom().Trim();
|
||||
Entity.Spawner?.AddToSpawnQueue(species, pos);
|
||||
spawnedAmount++;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
|
||||
{
|
||||
@@ -283,6 +288,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine);
|
||||
spawnedAmount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace Barotrauma.Items.Components
|
||||
a.Identifier.Equals(Effect, StringComparison.OrdinalIgnoreCase) ||
|
||||
a.AfflictionType.Equals(Effect, StringComparison.OrdinalIgnoreCase)).GetRandom();
|
||||
}
|
||||
Tainted = true;
|
||||
}
|
||||
|
||||
[Serialize(3.0f, false)]
|
||||
@@ -94,37 +95,28 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (targetCharacter != null) { return; }
|
||||
|
||||
if (tainted)
|
||||
{
|
||||
if (selectedTaintedEffect != null)
|
||||
{
|
||||
float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
|
||||
var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (existingAffliction != null)
|
||||
{
|
||||
existingAffliction.Strength = selectedTaintedEffectStrength;
|
||||
}
|
||||
targetCharacter = character;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (selectedEffect != null)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
|
||||
float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
|
||||
var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (existingAffliction != null)
|
||||
{
|
||||
existingAffliction.Strength = selectedEffectStrength;
|
||||
}
|
||||
targetCharacter = character;
|
||||
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
|
||||
float selectedEffectStrength = GetCombinedEffectStrength();
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
|
||||
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null) { affliction.Strength = selectedEffectStrength; }
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
if (tainted && selectedTaintedEffect != null)
|
||||
{
|
||||
float selectedTaintedEffectStrength = GetCombinedTaintedEffectStrength();
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
|
||||
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (affliction != null) { affliction.Strength = selectedTaintedEffectStrength; }
|
||||
targetCharacter = character;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
@@ -142,13 +134,14 @@ namespace Barotrauma.Items.Components
|
||||
(rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface)))
|
||||
{
|
||||
item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter);
|
||||
targetCharacter.CharacterHealth.ReduceAffliction(null, selectedEffect.Identifier, selectedEffect.MaxStrength);
|
||||
if (tainted)
|
||||
{
|
||||
targetCharacter.CharacterHealth.ReduceAffliction(null, selectedTaintedEffect.Identifier, selectedTaintedEffect.MaxStrength);
|
||||
}
|
||||
targetCharacter = null;
|
||||
IsActive = false;
|
||||
|
||||
var affliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null) { affliction.Strength = GetCombinedEffectStrength(); }
|
||||
var taintedAffliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (taintedAffliction != null) { taintedAffliction.Strength = GetCombinedTaintedEffectStrength(); }
|
||||
|
||||
targetCharacter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,6 +177,36 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private float GetCombinedEffectStrength()
|
||||
{
|
||||
float effectStrength = 0.0f;
|
||||
foreach (Item otherItem in targetCharacter.Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
var geneticMaterial = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial == null || !geneticMaterial.IsActive) { continue; }
|
||||
if (geneticMaterial.selectedEffect == selectedEffect)
|
||||
{
|
||||
effectStrength += otherItem.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
|
||||
}
|
||||
}
|
||||
return effectStrength;
|
||||
}
|
||||
|
||||
private float GetCombinedTaintedEffectStrength()
|
||||
{
|
||||
float taintedEffectStrength = 0.0f;
|
||||
foreach (Item otherItem in targetCharacter.Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
var geneticMaterial = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial == null || !geneticMaterial.IsActive) { continue; }
|
||||
if (selectedTaintedEffect != null && geneticMaterial.selectedTaintedEffect == selectedTaintedEffect)
|
||||
{
|
||||
taintedEffectStrength += otherItem.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
|
||||
}
|
||||
}
|
||||
return taintedEffectStrength;
|
||||
}
|
||||
|
||||
private float GetTaintedProbabilityOnRefine(Character user)
|
||||
{
|
||||
if (user == null) { return 1.0f; }
|
||||
|
||||
@@ -278,7 +278,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0, j = 0; i < maxSides; i++)
|
||||
{
|
||||
if (!occupiedSides.IsBitSet((TileSide) (1 << i)))
|
||||
if (!occupiedSides.HasFlag((TileSide) (1 << i)))
|
||||
{
|
||||
pool[j] = i;
|
||||
j++;
|
||||
@@ -303,7 +303,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool CanGrowMore() => (Sides | BlockedSides).Count() < 4;
|
||||
|
||||
public bool IsSideBlocked(TileSide side) => BlockedSides.IsBitSet(side) || Sides.IsBitSet(side);
|
||||
public bool IsSideBlocked(TileSide side) => BlockedSides.HasFlag(side) || Sides.HasFlag(side);
|
||||
|
||||
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size);
|
||||
}
|
||||
@@ -774,7 +774,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
TileSide oppositeSide = connectingSide.GetOppositeSide();
|
||||
|
||||
if (otherVine.BlockedSides.IsBitSet(connectingSide))
|
||||
if (otherVine.BlockedSides.HasFlag(connectingSide))
|
||||
{
|
||||
newVine.BlockedSides |= oppositeSide;
|
||||
continue;
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace Barotrauma.Items.Components
|
||||
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius, item.body.Density)
|
||||
{
|
||||
BodyType = BodyType.Dynamic,
|
||||
CollidesWith = Physics.CollisionCharacter,
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionProjectile,
|
||||
CollisionCategories = Physics.CollisionItemBlocking,
|
||||
Enabled = false,
|
||||
UserData = this
|
||||
@@ -604,7 +604,14 @@ namespace Barotrauma.Items.Components
|
||||
int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
|
||||
int currentlyAttachedCount = Item.ItemList.Count(
|
||||
i => i.Submarine == attachTarget?.Submarine && i.GetComponent<Holdable>() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier);
|
||||
if (currentlyAttachedCount >= maxAttachableCount)
|
||||
if (maxAttachableCount == 0)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage(TextManager.Get("itemmsgrequiretraining"), Color.Red);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
else if (currentlyAttachedCount >= maxAttachableCount)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage($"{TextManager.Get("itemmsgtotalnumberlimited")} ({currentlyAttachedCount}/{maxAttachableCount})", Color.Red);
|
||||
@@ -801,7 +808,7 @@ namespace Barotrauma.Items.Components
|
||||
equipLimb = picker.AnimController.GetLimb(LimbType.Torso);
|
||||
}
|
||||
|
||||
if (equipLimb != null)
|
||||
if (equipLimb != null && !equipLimb.Removed)
|
||||
{
|
||||
float itemAngle = (equipLimb.Rotation + holdAngle * picker.AnimController.Dir);
|
||||
|
||||
@@ -814,6 +821,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
handlePos[0].X = -handlePos[0].X;
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma.Items.Components
|
||||
reloadTimer /= (1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier));
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
|
||||
item.body.FarseerBody.OnCollision += OnCollision;
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
item.body.PhysEnabled = true;
|
||||
@@ -361,6 +361,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
hitTargets.Add(targetItem);
|
||||
}
|
||||
else if (f2.Body.UserData is Holdable holdable && holdable.CanPush)
|
||||
{
|
||||
hitTargets.Add(holdable.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@@ -411,6 +415,14 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem.Removed) { return; }
|
||||
Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (target.UserData is Holdable holdable && holdable.CanPush)
|
||||
{
|
||||
if (holdable.Item.Removed) { return; }
|
||||
Attack.DoDamage(User, holdable.Item, item.WorldPosition, 1.0f);
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
User = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (PickingTime > 0.0f)
|
||||
{
|
||||
var abilityPickingTime = new AbilityValueItem(PickingTime, item.Prefab);
|
||||
var abilityPickingTime = new AbilityItemPickingTime(PickingTime, item.Prefab);
|
||||
picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime);
|
||||
|
||||
if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
|
||||
@@ -300,4 +300,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityItemPickingTime : AbilityObject, IAbilityValue, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityItemPickingTime(float pickingTime, ItemPrefab itemPrefab)
|
||||
{
|
||||
Value = pickingTime;
|
||||
ItemPrefab = itemPrefab;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
|
||||
return MathHelper.ToRadians(spread);
|
||||
}
|
||||
|
||||
private readonly List<Body> limbBodies = new List<Body>();
|
||||
private readonly List<Body> ignoredBodies = new List<Body>();
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
tryingToCharge = true;
|
||||
@@ -172,8 +172,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (character != null)
|
||||
{
|
||||
var abilityItem = new AbilityItem(item);
|
||||
character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityItem);
|
||||
var abilityRangedWeapon = new AbilityRangedWeapon(item);
|
||||
character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityRangedWeapon);
|
||||
}
|
||||
|
||||
if (item.AiTarget != null)
|
||||
@@ -182,11 +182,20 @@ namespace Barotrauma.Items.Components
|
||||
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
|
||||
}
|
||||
|
||||
limbBodies.Clear();
|
||||
ignoredBodies.Clear();
|
||||
foreach (Limb l in character.AnimController.Limbs)
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
limbBodies.Add(l.body.FarseerBody);
|
||||
ignoredBodies.Add(l.body.FarseerBody);
|
||||
}
|
||||
|
||||
foreach (Item heldItem in character.HeldItems)
|
||||
{
|
||||
var holdable = heldItem.GetComponent<Holdable>();
|
||||
if (holdable?.Pusher != null)
|
||||
{
|
||||
ignoredBodies.Add(holdable.Pusher.FarseerBody);
|
||||
}
|
||||
}
|
||||
|
||||
float degreeOfFailure = 1.0f - DegreeOfSuccess(character);
|
||||
@@ -211,7 +220,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier);
|
||||
projectile.Launcher = item;
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier);
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
|
||||
if (i == 0)
|
||||
{
|
||||
@@ -270,4 +279,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void LaunchProjSpecific();
|
||||
}
|
||||
class AbilityRangedWeapon : AbilityObject, IAbilityItem
|
||||
{
|
||||
public AbilityRangedWeapon(Item item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
public Item Item { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +521,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; }
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure });
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, structure: targetStructure);
|
||||
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
|
||||
|
||||
float structureFixAmount = StructureFixAmount;
|
||||
@@ -589,8 +589,7 @@ namespace Barotrauma.Items.Components
|
||||
closestLimb.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
|
||||
}
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse,
|
||||
closestLimb == null ? new ISerializableEntity[] { targetCharacter } : new ISerializableEntity[] { targetCharacter, closestLimb });
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, character: targetCharacter, limb: closestLimb);
|
||||
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
|
||||
return true;
|
||||
}
|
||||
@@ -606,7 +605,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetLimb.character, targetLimb });
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, character: targetLimb.character, limb: targetLimb);
|
||||
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
|
||||
return true;
|
||||
}
|
||||
@@ -645,7 +644,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem);
|
||||
|
||||
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
@@ -682,7 +681,7 @@ namespace Barotrauma.Items.Components
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
if (leak.Submarine == null)
|
||||
if (leak.Submarine == null || leak.Submarine != character.Submarine)
|
||||
{
|
||||
Reset();
|
||||
return true;
|
||||
@@ -836,32 +835,44 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, IEnumerable<ISerializableEntity> targets)
|
||||
private static List<ISerializableEntity> currentTargets = new List<ISerializableEntity>();
|
||||
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, Item targetItem = null, Character character = null, Limb limb = null, Structure structure = null)
|
||||
{
|
||||
if (statusEffectLists == null) { return; }
|
||||
if (!statusEffectLists.TryGetValue(actionType, out List<StatusEffect> statusEffects)) { return; }
|
||||
|
||||
currentTargets.Clear();
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.SetUser(user);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets);
|
||||
if (targetItem != null)
|
||||
{
|
||||
currentTargets.AddRange(targetItem.AllPropertyObjects);
|
||||
}
|
||||
if (structure != null)
|
||||
{
|
||||
currentTargets.Add(structure);
|
||||
}
|
||||
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||
}
|
||||
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets.Where(t => t is Character));
|
||||
currentTargets.Add(character);
|
||||
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||
}
|
||||
else if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets.Where(t => t is Limb));
|
||||
currentTargets.Add(limb);
|
||||
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (user == null) { return; }
|
||||
// Hard-coded progress bars for welding doors stuck.
|
||||
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
||||
foreach (ISerializableEntity target in targets)
|
||||
foreach (ISerializableEntity target in currentTargets)
|
||||
{
|
||||
if (!(target is Door door)) { continue; }
|
||||
if (!door.CanBeWelded || !door.Item.IsInteractable(user)) { continue; }
|
||||
|
||||
@@ -284,6 +284,43 @@ namespace Barotrauma.Items.Components
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
ParseMsg();
|
||||
|
||||
string inheritRequiredSkillsFrom = element.GetAttributeString("inheritrequiredskillsfrom", "");
|
||||
if (!string.IsNullOrEmpty(inheritRequiredSkillsFrom))
|
||||
{
|
||||
var component = item.Components.Find(ic => ic.Name.Equals(inheritRequiredSkillsFrom, StringComparison.OrdinalIgnoreCase));
|
||||
if (component == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - component \"{name}\" is set to inherit its required skills from \"{inheritRequiredSkillsFrom}\", but a component of that type couldn't be found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredSkills = component.requiredSkills;
|
||||
}
|
||||
}
|
||||
|
||||
string inheritStatusEffectsFrom = element.GetAttributeString("inheritstatuseffectsfrom", "");
|
||||
if (!string.IsNullOrEmpty(inheritStatusEffectsFrom))
|
||||
{
|
||||
var component = item.Components.Find(ic => ic.Name.Equals(inheritStatusEffectsFrom, StringComparison.OrdinalIgnoreCase));
|
||||
if (component == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - component \"{name}\" is set to inherit its StatusEffects from \"{inheritStatusEffectsFrom}\", but a component of that type couldn't be found.");
|
||||
}
|
||||
else if (component.statusEffectLists != null)
|
||||
{
|
||||
statusEffectLists ??= new Dictionary<ActionType, List<StatusEffect>>();
|
||||
foreach (KeyValuePair<ActionType, List<StatusEffect>> kvp in component.statusEffectLists)
|
||||
{
|
||||
if (!statusEffectLists.TryGetValue(kvp.Key, out List<StatusEffect> effectList))
|
||||
{
|
||||
effectList = new List<StatusEffect>();
|
||||
statusEffectLists.Add(kvp.Key, effectList);
|
||||
}
|
||||
effectList.AddRange(kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -315,19 +352,8 @@ namespace Barotrauma.Items.Components
|
||||
requiredSkills.Add(new Skill(skillIdentifier, subElement.GetAttributeInt("level", 0)));
|
||||
break;
|
||||
case "statuseffect":
|
||||
var statusEffect = StatusEffect.Load(subElement, item.Name);
|
||||
|
||||
if (statusEffectLists == null) statusEffectLists = new Dictionary<ActionType, List<StatusEffect>>();
|
||||
|
||||
List<StatusEffect> effectList;
|
||||
if (!statusEffectLists.TryGetValue(statusEffect.type, out effectList))
|
||||
{
|
||||
effectList = new List<StatusEffect>();
|
||||
statusEffectLists.Add(statusEffect.type, effectList);
|
||||
}
|
||||
|
||||
effectList.Add(statusEffect);
|
||||
|
||||
statusEffectLists ??= new Dictionary<ActionType, List<StatusEffect>>();
|
||||
LoadStatusEffect(subElement);
|
||||
break;
|
||||
default:
|
||||
if (LoadElemProjSpecific(subElement)) { break; }
|
||||
@@ -342,6 +368,17 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadStatusEffect(XElement subElement)
|
||||
{
|
||||
var statusEffect = StatusEffect.Load(subElement, item.Name);
|
||||
if (!statusEffectLists.TryGetValue(statusEffect.type, out List<StatusEffect> effectList))
|
||||
{
|
||||
effectList = new List<StatusEffect>();
|
||||
statusEffectLists.Add(statusEffect.type, effectList);
|
||||
}
|
||||
effectList.Add(statusEffect);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActiveState(bool isActive)
|
||||
@@ -399,6 +436,8 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool UpdateWhenInactive => false;
|
||||
|
||||
//called when isActive is true and condition > 0.0f
|
||||
public virtual void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
@@ -798,7 +837,10 @@ namespace Barotrauma.Items.Components
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic.statusEffectLists == null || !ic.statusEffectLists.TryGetValue(ActionType.OnBroken, out List<StatusEffect> brokenEffects)) { continue; }
|
||||
brokenEffects.ForEach(e => e.SetUser(user));
|
||||
foreach (var brokenEffect in brokenEffects)
|
||||
{
|
||||
brokenEffect.SetUser(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,7 +1049,8 @@ namespace Barotrauma.Items.Components
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
// Prefer items with the same identifier as the contained items'
|
||||
return container.ContainsItemsWithSameIdentifier(i) ? 1.0f : 0.5f;
|
||||
}
|
||||
};
|
||||
containObjective.Abandoned += () => aiController.IgnoredItems.Add(container.Item);
|
||||
|
||||
@@ -208,12 +208,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
public List<RelatedItem> ContainableItems { get; }
|
||||
|
||||
public ItemContainer(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
int totalCapacity = capacity;
|
||||
|
||||
List<RelatedItem> containableItems = null;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -225,8 +226,8 @@ namespace Barotrauma.Items.Components
|
||||
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
|
||||
continue;
|
||||
}
|
||||
containableItems ??= new List<RelatedItem>();
|
||||
containableItems.Add(containable);
|
||||
ContainableItems ??= new List<RelatedItem>();
|
||||
ContainableItems.Add(containable);
|
||||
break;
|
||||
case "subcontainer":
|
||||
totalCapacity += subElement.GetAttributeInt("capacity", 1);
|
||||
@@ -237,7 +238,7 @@ namespace Barotrauma.Items.Components
|
||||
slotRestrictions = new SlotRestrictions[totalCapacity];
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
slotRestrictions[i] = new SlotRestrictions(maxStackSize, containableItems);
|
||||
slotRestrictions[i] = new SlotRestrictions(maxStackSize, ContainableItems);
|
||||
}
|
||||
|
||||
int subContainerIndex = capacity;
|
||||
@@ -344,6 +345,19 @@ namespace Barotrauma.Items.Components
|
||||
return slotRestrictions[index].MatchesItem(itemPrefab);
|
||||
}
|
||||
|
||||
public bool ContainsItemsWithSameIdentifier(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
foreach (var containedItem in Inventory.AllItems)
|
||||
{
|
||||
if (containedItem.Prefab.Identifier == item.Prefab.Identifier)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -432,7 +446,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
var abilityItem = new AbilityItem(item);
|
||||
var abilityItem = new AbilityItemContainer(item);
|
||||
character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem);
|
||||
|
||||
return base.Select(character);
|
||||
@@ -494,6 +508,21 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "activate":
|
||||
case "use":
|
||||
case "trigger_in":
|
||||
if (signal.value != "0")
|
||||
{
|
||||
item.Use(1.0f, signal.sender);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetContainedItemPositions()
|
||||
{
|
||||
Vector2 transformedItemPos = ItemPos * item.Scale;
|
||||
@@ -689,7 +718,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void ShallowRemoveComponentSpecific()
|
||||
{
|
||||
}
|
||||
@@ -743,4 +771,13 @@ namespace Barotrauma.Items.Components
|
||||
return componentElement;
|
||||
}
|
||||
}
|
||||
|
||||
class AbilityItemContainer : AbilityObject, IAbilityItem
|
||||
{
|
||||
public AbilityItemContainer(Item item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
public Item Item { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,18 +370,29 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Item GetFocusTarget()
|
||||
{
|
||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), "position_out");
|
||||
|
||||
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||
Item focusTarget = null;
|
||||
for (int c = 0; c < 2; c++)
|
||||
{
|
||||
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; }
|
||||
if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected)
|
||||
//try finding the item to focus on using trigger_out, and if that fails, using position_out
|
||||
string connectionName = c == 0 ? "trigger_out" : "position_out";
|
||||
string signal = c == 0 ? "0" : MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture);
|
||||
if (!item.SendSignal(new Signal(signal, sender: user), connectionName) || focusTarget != null)
|
||||
{
|
||||
return item.LastSentSignalRecipients[i].Item;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; }
|
||||
if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected)
|
||||
{
|
||||
focusTarget = item.LastSentSignalRecipients[i].Item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
return focusTarget;
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
|
||||
+26
-4
@@ -170,7 +170,7 @@ namespace Barotrauma.Items.Components
|
||||
character.CheckTalents(AbilityEffectType.OnItemDeconstructedByAlly, abilityTargetItem);
|
||||
}
|
||||
|
||||
var itemCreationMultiplier = new AbilityValueItem(amountMultiplier, targetItem.Prefab);
|
||||
var itemCreationMultiplier = new AbilityItemCreationMultiplier(targetItem.Prefab, amountMultiplier);
|
||||
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemCreationMultiplier);
|
||||
amountMultiplier = (int)itemCreationMultiplier.Value;
|
||||
}
|
||||
@@ -261,8 +261,8 @@ namespace Barotrauma.Items.Components
|
||||
if (user != null && !user.Removed)
|
||||
{
|
||||
// used to spawn items directly into the deconstructor
|
||||
var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab);
|
||||
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer);
|
||||
var itemDeconstructedInventory = new AbilityItemDeconstructedInventory(targetItem.Prefab, item);
|
||||
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemDeconstructedInventory);
|
||||
}
|
||||
|
||||
int amount = (int)amountMultiplier;
|
||||
@@ -333,7 +333,7 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
if (containedItem?.OwnInventory != null && containedItem.OwnInventory.TryPutItem(item, user: null))
|
||||
if (containedItem?.OwnInventory != null && containedItem.GetComponent<GeneticMaterial>() == null && containedItem.OwnInventory.TryPutItem(item, user: null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -454,4 +454,26 @@ namespace Barotrauma.Items.Components
|
||||
public Character Character { get; set; }
|
||||
}
|
||||
|
||||
class AbilityItemCreationMultiplier : AbilityObject, IAbilityValue, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityItemCreationMultiplier(ItemPrefab itemPrefab, float itemAmountMultiplier)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Value = itemAmountMultiplier;
|
||||
}
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
public float Value { get; set; }
|
||||
}
|
||||
|
||||
class AbilityItemDeconstructedInventory : AbilityObject, IAbilityItem, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityItemDeconstructedInventory(ItemPrefab itemPrefab, Item item)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Item = item;
|
||||
}
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
public Item Item { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -179,8 +179,6 @@ namespace Barotrauma.Items.Components
|
||||
if (selectedItem == null) { return; }
|
||||
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
|
||||
|
||||
RefreshAvailableIngredients();
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
activateButton.Text = TextManager.Get("FabricatorCancel");
|
||||
@@ -189,7 +187,13 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
this.user = user;
|
||||
fabricatedItem = selectedItem;
|
||||
MoveIngredientsToInputContainer(selectedItem);
|
||||
RefreshAvailableIngredients();
|
||||
|
||||
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
if (!isClient)
|
||||
{
|
||||
MoveIngredientsToInputContainer(selectedItem);
|
||||
}
|
||||
|
||||
requiredTime = GetRequiredTime(fabricatedItem, user);
|
||||
timeUntilReady = requiredTime;
|
||||
@@ -230,21 +234,19 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
if (fabricatedItem == null) { return; }
|
||||
fabricatedItem = null;
|
||||
|
||||
#if CLIENT
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#elif CLIENT
|
||||
itemList.Enabled = true;
|
||||
if (activateButton != null)
|
||||
{
|
||||
activateButton.Text = TextManager.Get("FabricatorCreate");
|
||||
}
|
||||
#endif
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
fabricatedItem = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -256,15 +258,20 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
refreshIngredientsTimer -= deltaTime;
|
||||
|
||||
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
|
||||
bool isClient = GameMain.NetworkMember?.IsClient ?? false;
|
||||
|
||||
if (!isClient)
|
||||
{
|
||||
CancelFabricating();
|
||||
return;
|
||||
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
|
||||
{
|
||||
CancelFabricating();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
|
||||
|
||||
if (GameMain.NetworkMember?.IsClient ?? false)
|
||||
if (isClient)
|
||||
{
|
||||
hasPower = State != FabricatorState.Paused;
|
||||
if (!hasPower)
|
||||
@@ -365,28 +372,29 @@ namespace Barotrauma.Items.Components
|
||||
availableItems.Remove(availableItem);
|
||||
Entity.Spawner.AddToRemoveQueue(availableItem);
|
||||
inputContainer.Inventory.RemoveItem(availableItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
|
||||
|
||||
var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem);
|
||||
var fabricationitemAmount = new AbilityFabricationItemAmount(fabricatedItem.TargetItem, fabricatedItem.Amount);
|
||||
|
||||
int quality = 0;
|
||||
if (user?.Info != null)
|
||||
{
|
||||
foreach (Character character in Character.GetFriendlyCrew(user))
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem);
|
||||
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationitemAmount);
|
||||
}
|
||||
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
|
||||
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationitemAmount);
|
||||
|
||||
quality = GetFabricatedItemQuality(fabricatedItem, user);
|
||||
}
|
||||
|
||||
var tempUser = user;
|
||||
for (int i = 0; i < (int)fabricationValueItem.Value; i++)
|
||||
for (int i = 0; i < (int)fabricationitemAmount.Value; i++)
|
||||
{
|
||||
float outCondition = fabricatedItem.OutCondition;
|
||||
if (i < amountFittingContainer)
|
||||
@@ -433,7 +441,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float userSkill = user.GetSkillLevel(skill.Identifier);
|
||||
float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f);
|
||||
var addedSkillValue = new AbilityValueString(addedSkill, skill.Identifier);
|
||||
var addedSkillValue = new AbilityFabricatorSkillGain(skill.Identifier, addedSkill);
|
||||
user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue);
|
||||
|
||||
user.Info.IncreaseSkillLevel(
|
||||
@@ -542,6 +550,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void RefreshAvailableIngredients()
|
||||
{
|
||||
Character user = this.user;
|
||||
#if CLIENT
|
||||
user ??= Character.Controlled;
|
||||
#endif
|
||||
|
||||
List<Item> itemList = new List<Item>();
|
||||
itemList.AddRange(inputContainer.Inventory.AllItems);
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
@@ -550,6 +563,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
if (user != null)
|
||||
{
|
||||
if (!itemContainer.HasRequiredItems(user, addMessage: false)) { continue; }
|
||||
}
|
||||
|
||||
var deconstructor = linkedItem.GetComponent<Deconstructor>();
|
||||
if (deconstructor != null)
|
||||
@@ -568,17 +585,10 @@ namespace Barotrauma.Items.Components
|
||||
itemList.AddRange(container.Inventory.AllItems);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (Character.Controlled?.Inventory != null)
|
||||
{
|
||||
itemList.AddRange(Character.Controlled.Inventory.AllItems);
|
||||
}
|
||||
#else
|
||||
if (user?.Inventory != null)
|
||||
{
|
||||
itemList.AddRange(user.Inventory.AllItems);
|
||||
}
|
||||
#endif
|
||||
availableIngredients.Clear();
|
||||
foreach (Item item in itemList)
|
||||
{
|
||||
@@ -600,8 +610,6 @@ namespace Barotrauma.Items.Components
|
||||
//required ingredients that are already present in the input container
|
||||
List<Item> usedItems = new List<Item>();
|
||||
|
||||
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
|
||||
targetItem.RequiredItems.ForEach(requiredItem => {
|
||||
for (int i = 0; i < requiredItem.Amount; i++)
|
||||
{
|
||||
@@ -630,10 +638,11 @@ namespace Barotrauma.Items.Components
|
||||
if (!inputContainer.Inventory.CanBePut(availablePrefab))
|
||||
{
|
||||
var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it));
|
||||
unneededItem?.Drop(null, createNetworkEvent: !isClient);
|
||||
unneededItem?.Drop(null);
|
||||
}
|
||||
inputContainer.Inventory.TryPutItem(availablePrefab, user: null, createNetworkEvent: !isClient);
|
||||
inputContainer.Inventory.TryPutItem(availablePrefab, user: null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -684,5 +693,26 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
savedFabricatedItem = null;
|
||||
}
|
||||
class AbilityFabricatorSkillGain : AbilityObject, IAbilityValue, IAbilitySkillIdentifier
|
||||
{
|
||||
public AbilityFabricatorSkillGain(string skillIdentifier, float skillAmount)
|
||||
{
|
||||
SkillIdentifier = skillIdentifier;
|
||||
Value = skillAmount;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public string SkillIdentifier { get; set; }
|
||||
}
|
||||
|
||||
class AbilityFabricationItemAmount : AbilityObject, IAbilityValue, IAbilityItemPrefab
|
||||
{
|
||||
public AbilityFabricationItemAmount(ItemPrefab itemPrefab, float itemAmount)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Value = itemAmount;
|
||||
}
|
||||
public float Value { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public float CurrentBrokenVolume
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item.ConditionPercentage > 10.0f || !IsActive) { return 0.0f; }
|
||||
return (1.0f - item.ConditionPercentage / 10.0f) * 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private float pumpSpeedLockTimer, isActiveLockTimer;
|
||||
|
||||
[Serialize(0.0f, true, description: "How fast the item is currently pumping water (-100 = full speed out, 100 = full speed in). Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
@@ -72,6 +81,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private const float TinkeringSpeedIncrease = 4.0f;
|
||||
|
||||
public override bool UpdateWhenInactive => true;
|
||||
|
||||
public Pump(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -82,11 +93,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
pumpSpeedLockTimer -= deltaTime;
|
||||
isActiveLockTimer -= deltaTime;
|
||||
|
||||
if (!IsActive) { return; }
|
||||
|
||||
currFlow = 0.0f;
|
||||
|
||||
if (TargetLevel != null)
|
||||
{
|
||||
pumpSpeedLockTimer -= deltaTime;
|
||||
float hullPercentage = 0.0f;
|
||||
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
|
||||
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (lastUser == value) { return; }
|
||||
lastUser = value;
|
||||
degreeOfSuccess = lastUser == null ? 0.0f : DegreeOfSuccess(lastUser);
|
||||
degreeOfSuccess = lastUser == null ? 0.0f : Math.Min(DegreeOfSuccess(lastUser), 1.0f);
|
||||
LastUserWasPlayer = lastUser.IsPlayer;
|
||||
}
|
||||
}
|
||||
@@ -601,7 +601,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!shutDown)
|
||||
{
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
float degreeOfSuccess = Math.Min(DegreeOfSuccess(character), 1.0f);
|
||||
float refuelLimit = 0.3f;
|
||||
//characters with insufficient skill levels don't refuel the reactor
|
||||
if (degreeOfSuccess > refuelLimit)
|
||||
|
||||
@@ -106,6 +106,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false, description: "Should the sonar view be centered on the transducers or the submarine's center of mass. Only has an effect if UseTransducers is enabled.")]
|
||||
public bool CenterOnTransducers
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false, description: "Does the sonar have mineral scanning mode. " +
|
||||
"Only available in-game when the Item has no Steering component.")]
|
||||
public bool HasMineralScanner { get; set; }
|
||||
@@ -318,7 +325,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 transducerPosSum = Vector2.Zero;
|
||||
foreach (ConnectedTransducer transducer in connectedTransducers)
|
||||
{
|
||||
if (transducer.Transducer.Item.Submarine != null)
|
||||
if (transducer.Transducer.Item.Submarine != null && CenterOnTransducers)
|
||||
{
|
||||
return transducer.Transducer.Item.Submarine.WorldPosition;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
// override autopilot pathing while the AI rams, and go full speed ahead
|
||||
if (AIRamTimer > 0f)
|
||||
if (AIRamTimer > 0f && controlledSub != null)
|
||||
{
|
||||
AIRamTimer -= deltaTime;
|
||||
TargetVelocity = GetSteeringVelocity(AITacticalTarget, 0f);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -111,6 +110,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, true, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
|
||||
public bool ExponentialRechargeSpeed { get; set; }
|
||||
|
||||
private float efficiency;
|
||||
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, true, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
|
||||
public float Efficiency
|
||||
{
|
||||
get { return efficiency; }
|
||||
set { efficiency = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public float RechargeRatio => RechargeSpeed / MaxRechargeSpeed;
|
||||
|
||||
public const float aiRechargeTargetRatio = 0.5f;
|
||||
@@ -170,7 +180,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
}
|
||||
|
||||
|
||||
if (charge >= capacity)
|
||||
{
|
||||
//rechargeVoltage = 0.0f;
|
||||
@@ -181,13 +191,17 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float missingCharge = capacity - charge;
|
||||
float targetRechargeSpeed = rechargeSpeed;
|
||||
if (ExponentialRechargeSpeed)
|
||||
{
|
||||
targetRechargeSpeed = MathF.Pow(rechargeSpeed / maxRechargeSpeed, 2) * maxRechargeSpeed;
|
||||
}
|
||||
if (missingCharge < 1.0f)
|
||||
{
|
||||
targetRechargeSpeed *= missingCharge;
|
||||
}
|
||||
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, targetRechargeSpeed, 0.05f);
|
||||
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f;
|
||||
}
|
||||
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f * efficiency;
|
||||
}
|
||||
|
||||
if (charge <= 0.0f)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public List<Connection> PowerConnections { get; private set; }
|
||||
|
||||
private readonly HashSet<Connection> signalConnections = new HashSet<Connection>();
|
||||
|
||||
private readonly Dictionary<Connection, bool> connectionDirty = new Dictionary<Connection, bool>();
|
||||
|
||||
//a list of connections a given connection is connected to, either directly or via other power transfer components
|
||||
@@ -121,6 +123,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void InitProjectSpecific(XElement element);
|
||||
|
||||
private static readonly HashSet<PowerTransfer> recipientsToRefresh = new HashSet<PowerTransfer>();
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
@@ -132,7 +135,8 @@ namespace Barotrauma.Items.Components
|
||||
powerLoad = 0.0f;
|
||||
currPowerConsumption = 0.0f;
|
||||
SetAllConnectionsDirty();
|
||||
foreach (HashSet<Connection> recipientList in connectedRecipients.Values.ToList())
|
||||
recipientsToRefresh.Clear();
|
||||
foreach (HashSet<Connection> recipientList in connectedRecipients.Values)
|
||||
{
|
||||
foreach (Connection c in recipientList)
|
||||
{
|
||||
@@ -140,16 +144,26 @@ namespace Barotrauma.Items.Components
|
||||
var recipientPowerTransfer = c.Item.GetComponent<PowerTransfer>();
|
||||
if (recipientPowerTransfer != null)
|
||||
{
|
||||
recipientPowerTransfer.SetAllConnectionsDirty();
|
||||
recipientPowerTransfer.RefreshConnections();
|
||||
recipientsToRefresh.Add(recipientPowerTransfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (PowerTransfer recipientPowerTransfer in recipientsToRefresh)
|
||||
{
|
||||
recipientPowerTransfer.SetAllConnectionsDirty();
|
||||
recipientPowerTransfer.RefreshConnections();
|
||||
}
|
||||
RefreshConnections();
|
||||
isBroken = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int prevSentPowerValue;
|
||||
private string powerSignal;
|
||||
private int prevSentLoadValue;
|
||||
private string loadSignal;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
RefreshConnections();
|
||||
@@ -172,6 +186,19 @@ namespace Barotrauma.Items.Components
|
||||
//if the item can't be fixed, don't allow it to break
|
||||
if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
|
||||
|
||||
if (prevSentPowerValue != (int)-CurrPowerConsumption || powerSignal == null)
|
||||
{
|
||||
prevSentPowerValue = (int)Math.Round(-CurrPowerConsumption);
|
||||
powerSignal = prevSentPowerValue.ToString();
|
||||
}
|
||||
if (prevSentLoadValue != (int)powerLoad || loadSignal == null)
|
||||
{
|
||||
prevSentLoadValue = (int)Math.Round(powerLoad);
|
||||
loadSignal = prevSentLoadValue.ToString();
|
||||
}
|
||||
item.SendSignal(powerSignal, "power_value_out");
|
||||
item.SendSignal(loadSignal, "load_value_out");
|
||||
|
||||
float maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
|
||||
Overload = -currPowerConsumption > Math.Max(powerLoad, 200.0f) * maxOverVoltage;
|
||||
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
@@ -217,6 +244,7 @@ namespace Barotrauma.Items.Components
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
private static readonly HashSet<Connection> tempConnected = new HashSet<Connection>();
|
||||
protected void RefreshConnections()
|
||||
{
|
||||
var connections = item.Connections;
|
||||
@@ -229,15 +257,15 @@ namespace Barotrauma.Items.Components
|
||||
else if (!connectionDirty[c])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//find all connections that are connected to this one (directly or via another PowerTransfer)
|
||||
HashSet<Connection> connected = new HashSet<Connection>();
|
||||
tempConnected.Clear();
|
||||
if (item.Condition > 0.0f)
|
||||
{
|
||||
if (!connectedRecipients.ContainsKey(c))
|
||||
{
|
||||
connectedRecipients.Add(c, connected);
|
||||
connectedRecipients.Add(c, tempConnected);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -249,24 +277,22 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
connected.Add(c);
|
||||
GetConnected(c, connected);
|
||||
tempConnected.Add(c);
|
||||
GetConnected(c, tempConnected);
|
||||
}
|
||||
connectedRecipients[c] = connected;
|
||||
connectedRecipients[c] = tempConnected;
|
||||
|
||||
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
|
||||
//(no need to go through the recursive GetConnected method again)
|
||||
foreach (Connection recipient in connected)
|
||||
foreach (Connection recipient in tempConnected)
|
||||
{
|
||||
if (recipient == c) { continue; }
|
||||
var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
|
||||
if (recipientPowerTransfer == null) continue;
|
||||
|
||||
if (recipientPowerTransfer == null) { continue; }
|
||||
if (!connectedRecipients.ContainsKey(recipient))
|
||||
{
|
||||
connectedRecipients.Add(recipient, connected);
|
||||
connectedRecipients.Add(recipient, tempConnected);
|
||||
}
|
||||
|
||||
recipientPowerTransfer.connectedRecipients[recipient] = connected;
|
||||
recipientPowerTransfer.connectionDirty[recipient] = false;
|
||||
}
|
||||
}
|
||||
@@ -296,7 +322,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void SetAllConnectionsDirty()
|
||||
{
|
||||
if (item.Connections == null) return;
|
||||
if (item.Connections == null) { return; }
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
connectionDirty[c] = true;
|
||||
@@ -321,6 +347,14 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Connection c in connections)
|
||||
{
|
||||
if (c.Name.Length > 5 && c.Name.Substring(0, 6) == "signal")
|
||||
{
|
||||
signalConnections.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(this is RelayComponent))
|
||||
{
|
||||
if (PowerConnections.Any(p => !p.IsOutput) && PowerConnections.Any(p => p.IsOutput))
|
||||
@@ -356,29 +390,30 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (item.Condition <= 0.0f || connection.IsPower) { return; }
|
||||
if (!connectedRecipients.ContainsKey(connection)) { return; }
|
||||
if (!signalConnections.Contains(connection)) { return; }
|
||||
|
||||
if (connection.Name.Length > 5 && connection.Name.Substring(0, 6) == "signal")
|
||||
foreach (Connection recipient in connectedRecipients[connection])
|
||||
{
|
||||
foreach (Connection recipient in connectedRecipients[connection])
|
||||
if (recipient.Item == item || recipient.Item == signal.source) { continue; }
|
||||
|
||||
signal.source?.LastSentSignalRecipients.Add(recipient);
|
||||
|
||||
foreach (ItemComponent ic in recipient.Item.Components)
|
||||
{
|
||||
if (recipient.Item == item || recipient.Item == signal.source) { continue; }
|
||||
|
||||
signal.source?.LastSentSignalRecipients.Add(recipient);
|
||||
|
||||
foreach (ItemComponent ic in recipient.Item.Components)
|
||||
{
|
||||
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||
if (ic is PowerTransfer && !(ic is RelayComponent)) { continue; }
|
||||
ic.ReceiveSignal(signal, recipient);
|
||||
}
|
||||
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||
if (ic is PowerTransfer && !(ic is RelayComponent)) { continue; }
|
||||
ic.ReceiveSignal(signal, recipient);
|
||||
}
|
||||
|
||||
if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
|
||||
{
|
||||
foreach (StatusEffect effect in recipient.Effects)
|
||||
{
|
||||
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
|
||||
@@ -272,7 +272,7 @@ namespace Barotrauma.Items.Components
|
||||
powered.voltage = -pt1.CurrPowerConsumption / Math.Max(pt1.PowerLoad, 1.0f);
|
||||
continue;
|
||||
}
|
||||
if (powered.powerConsumption <= 0.0f && !(powered is PowerContainer))
|
||||
if ((powered.powerConsumption <= 0.0f || (powered.Item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering && repairable.TinkeringPowersDevices)) && !(powered is PowerContainer))
|
||||
{
|
||||
powered.voltage = 1.0f;
|
||||
continue;
|
||||
|
||||
@@ -268,7 +268,8 @@ namespace Barotrauma.Items.Components
|
||||
IgnoredBodies = ignoredBodies;
|
||||
Vector2 projectilePos = weaponPos;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(weaponPos, spawnPos, IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
if (Submarine.PickBody(weaponPos, spawnPos, IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking,
|
||||
customPredicate: (Fixture f) => { return !IgnoredBodies.Contains(f.Body); }) == null)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = spawnPos;
|
||||
@@ -359,7 +360,7 @@ namespace Barotrauma.Items.Components
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
|
||||
IsActive = true;
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace Barotrauma.Items.Components
|
||||
private float deteriorationTimer;
|
||||
private float deteriorateAlwaysResetTimer;
|
||||
|
||||
private int prevSentConditionValue;
|
||||
private string conditionSignal;
|
||||
|
||||
bool wasBroken;
|
||||
bool wasGoodCondition;
|
||||
|
||||
@@ -113,6 +116,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float TinkeringStrength => tinkeringStrength;
|
||||
|
||||
private bool tinkeringPowersDevices;
|
||||
public bool TinkeringPowersDevices => tinkeringPowersDevices;
|
||||
|
||||
public bool IsBelowRepairThreshold => item.ConditionPercentage <= RepairThreshold;
|
||||
public bool IsBelowRepairIconThreshold => item.ConditionPercentage <= RepairThreshold / 2;
|
||||
|
||||
@@ -266,6 +272,7 @@ namespace Barotrauma.Items.Components
|
||||
if (action == FixActions.Tinker)
|
||||
{
|
||||
tinkeringStrength = 1f + CurrentFixer.GetStatValue(StatTypes.TinkeringStrength);
|
||||
tinkeringPowersDevices = CurrentFixer.HasAbilityFlag(AbilityFlags.TinkeringPowersDevices);
|
||||
|
||||
if (character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors) && item.GetComponent<Deconstructor>() != null || item.GetComponent<Fabricator>() != null)
|
||||
{
|
||||
@@ -346,7 +353,13 @@ namespace Barotrauma.Items.Components
|
||||
UpdateProjSpecific(deltaTime);
|
||||
IsTinkering = false;
|
||||
|
||||
item.SendSignal($"{(int) item.ConditionPercentage}", "condition_out");
|
||||
if (prevSentConditionValue != (int)item.ConditionPercentage || conditionSignal == null)
|
||||
{
|
||||
prevSentConditionValue = (int)item.ConditionPercentage;
|
||||
conditionSignal = prevSentConditionValue.ToString();
|
||||
}
|
||||
|
||||
item.SendSignal(conditionSignal, "condition_out");
|
||||
|
||||
if (CurrentFixer == null)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
protected readonly Character[] signalSender = new Character[2];
|
||||
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The item sends the output if both inputs have received a non-zero signal within the timeframe. If set to 0, the inputs must receive a signal at the same time.", alwaysUseInstanceValues: true)]
|
||||
public float TimeFrame
|
||||
@@ -80,14 +82,14 @@ namespace Barotrauma.Items.Components
|
||||
bool sendOutput = true;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] > timeFrame) sendOutput = false;
|
||||
if (timeSinceReceived[i] > timeFrame) { sendOutput = false; }
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
string signalOut = sendOutput ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut)) return;
|
||||
if (string.IsNullOrEmpty(signalOut)) { return; }
|
||||
|
||||
item.SendSignal(signalOut, "signal_out");
|
||||
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
@@ -95,12 +97,14 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "signal_in1":
|
||||
if (signal.value == "0") return;
|
||||
if (signal.value == "0") { return; }
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
signalSender[0] = signal.sender;
|
||||
break;
|
||||
case "signal_in2":
|
||||
if (signal.value == "0") return;
|
||||
if (signal.value == "0") { return; }
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
signalSender[1] = signal.sender;
|
||||
break;
|
||||
case "set_output":
|
||||
output = signal.value;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
|
||||
private HashSet<ItemPrefab> ActivatingItemPrefabs { get; set; } = new HashSet<ItemPrefab>();
|
||||
|
||||
|
||||
private bool AllowUsingButtons => ActivatingItemPrefabs.None() || Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab));
|
||||
private bool AllowUsingButtons => ActivatingItemPrefabs.None() || (Container != null && Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab)));
|
||||
|
||||
public ButtonTerminal(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
@@ -101,12 +101,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void OnItemLoadedProjSpecific();
|
||||
|
||||
private bool SendSignal(int signalIndex, bool isServerMessage = false)
|
||||
private bool SendSignal(int signalIndex, Character sender, bool isServerMessage = false)
|
||||
{
|
||||
if (!isServerMessage && !AllowUsingButtons) { return false; }
|
||||
string signal = Signals[signalIndex];
|
||||
string connectionName = $"signal_out{signalIndex + 1}";
|
||||
item.SendSignal(signal, connectionName);
|
||||
item.SendSignal(new Signal(signal, sender: sender), connectionName);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -17,6 +17,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("", false)]
|
||||
public string Separator
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ConcatComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
@@ -25,7 +31,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override string Calculate(string signal1, string signal2)
|
||||
{
|
||||
string output = signal1 + signal2;
|
||||
string output;
|
||||
if (string.IsNullOrEmpty(Separator))
|
||||
{
|
||||
output = signal1 + signal2;
|
||||
}
|
||||
else
|
||||
{
|
||||
output = signal1 + Separator + signal2;
|
||||
}
|
||||
return output.Length <= maxOutputLength ? output : output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return wires; }
|
||||
}
|
||||
|
||||
private Item item;
|
||||
private readonly Item item;
|
||||
|
||||
public readonly bool IsOutput;
|
||||
|
||||
@@ -142,7 +142,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
|
||||
|
||||
Effects = new List<StatusEffect>();
|
||||
|
||||
wireId = new ushort[MaxWires];
|
||||
|
||||
@@ -164,6 +163,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
break;
|
||||
case "statuseffect":
|
||||
Effects ??= new List<StatusEffect>();
|
||||
Effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name));
|
||||
break;
|
||||
}
|
||||
@@ -272,7 +272,7 @@ namespace Barotrauma.Items.Components
|
||||
ic.ReceiveSignal(signal, connection);
|
||||
}
|
||||
|
||||
if (signal.value != "0")
|
||||
if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
|
||||
{
|
||||
foreach (StatusEffect effect in recipient.Effects)
|
||||
{
|
||||
|
||||
+13
-1
@@ -24,7 +24,19 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public bool AlwaysAllowRewiring
|
||||
{
|
||||
get { return item.Submarine?.Info.Type == SubmarineType.BeaconStation; }
|
||||
get
|
||||
{
|
||||
if (item.Submarine == null) { return true; }
|
||||
switch (item.Submarine.Info.Type)
|
||||
{
|
||||
case SubmarineType.Wreck:
|
||||
case SubmarineType.BeaconStation:
|
||||
case SubmarineType.EnemySubmarine:
|
||||
case SubmarineType.Ruin:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true, description: "Locked connection panels cannot be rewired in-game.", alwaysUseInstanceValues: true)]
|
||||
|
||||
@@ -301,7 +301,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateProjSpecific();
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
{
|
||||
if (!ciElement.ContinuousSignal) { continue; }
|
||||
@@ -318,8 +317,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -24,7 +25,7 @@ namespace Barotrauma.Items.Components
|
||||
private int signalQueueSize;
|
||||
private int delayTicks;
|
||||
|
||||
private readonly Queue<DelayedSignal> signalQueue;
|
||||
private readonly Queue<DelayedSignal> signalQueue = new Queue<DelayedSignal>();
|
||||
|
||||
private DelayedSignal prevQueuedSignal;
|
||||
|
||||
@@ -39,6 +40,7 @@ namespace Barotrauma.Items.Components
|
||||
delay = value;
|
||||
delayTicks = (int)(delay / Timing.Step);
|
||||
signalQueueSize = Math.Max(delayTicks, 1) * 2;
|
||||
signalQueue.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +61,6 @@ namespace Barotrauma.Items.Components
|
||||
public DelayComponent(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
signalQueue = new Queue<DelayedSignal>();
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var signalOut = signalQueue.Peek();
|
||||
signalOut.SendDuration -= 1;
|
||||
item.SendSignal(new Signal(signalOut.Signal.value, strength: signalOut.Signal.strength), "signal_out");
|
||||
item.SendSignal(new Signal(signalOut.Signal.value, sender: signalOut.Signal.sender, strength: signalOut.Signal.strength), "signal_out");
|
||||
if (signalOut.SendDuration <= 0)
|
||||
{
|
||||
signalQueue.Dequeue();
|
||||
@@ -115,7 +116,7 @@ namespace Barotrauma.Items.Components
|
||||
signalQueue.Enqueue(prevQueuedSignal);
|
||||
break;
|
||||
case "set_delay":
|
||||
if (float.TryParse(signal.value, out float newDelay))
|
||||
if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float newDelay))
|
||||
{
|
||||
newDelay = MathHelper.Clamp(newDelay, 0, 60);
|
||||
if (signalQueue.Count > 0 && newDelay != Delay)
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected string[] receivedSignal;
|
||||
|
||||
private readonly Character[] signalSender = new Character[2];
|
||||
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
@@ -90,9 +92,8 @@ namespace Barotrauma.Items.Components
|
||||
if (sendOutput)
|
||||
{
|
||||
string signalOut = receivedSignal[0] == receivedSignal[1] ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut)) return;
|
||||
|
||||
item.SendSignal(signalOut, "signal_out");
|
||||
if (string.IsNullOrEmpty(signalOut)) { return; }
|
||||
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +104,15 @@ namespace Barotrauma.Items.Components
|
||||
case "signal_in1":
|
||||
receivedSignal[0] = signal.value;
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
signalSender[0] = signal.sender;
|
||||
break;
|
||||
case "signal_in2":
|
||||
receivedSignal[1] = signal.value;
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
signalSender[1] = signal.sender;
|
||||
break;
|
||||
case "set_output":
|
||||
output = signal.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -32,10 +32,22 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
base.ReceiveSignal(signal, connection);
|
||||
float.TryParse(receivedSignal[0], NumberStyles.Float, CultureInfo.InvariantCulture, out val1);
|
||||
float.TryParse(receivedSignal[1], NumberStyles.Float, CultureInfo.InvariantCulture, out val2);
|
||||
{
|
||||
//base.ReceiveSignal(signal, connection);
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "signal_in1":
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out val1);
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
break;
|
||||
case "signal_in2":
|
||||
float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out val2);
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
break;
|
||||
case "set_output":
|
||||
output = signal.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
SetLightSourceTransform();
|
||||
SetLightSourceTransformProjSpecific();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
SetLightSourceTransform();
|
||||
SetLightSourceTransformProjSpecific();
|
||||
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null && !body.Enabled)
|
||||
@@ -338,7 +338,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void SetLightSourceState(bool enabled, float brightness);
|
||||
|
||||
partial void SetLightSourceTransform();
|
||||
public void SetLightSourceTransform()
|
||||
{
|
||||
SetLightSourceTransformProjSpecific();
|
||||
}
|
||||
|
||||
partial void SetLightSourceTransformProjSpecific();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 TransformedDetectOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 transformedDetectOffset = detectOffset;
|
||||
if (item.FlippedX) { transformedDetectOffset.X = -transformedDetectOffset.X; }
|
||||
if (item.FlippedY) { transformedDetectOffset.Y = -transformedDetectOffset.Y; }
|
||||
return transformedDetectOffset;
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 100.0f, DecimalCount = 2), Serialize(0.1f, true, description: "How often the sensor checks if there's something moving near it. Higher values are better for performance.", alwaysUseInstanceValues: true)]
|
||||
public float UpdateInterval
|
||||
{
|
||||
@@ -184,15 +195,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 detectPos = item.WorldPosition + detectOffset;
|
||||
Vector2 detectPos = item.WorldPosition + TransformedDetectOffset;
|
||||
Rectangle detectRect = new Rectangle((int)(detectPos.X - rangeX), (int)(detectPos.Y - rangeY), (int)(rangeX * 2), (int)(rangeY * 2));
|
||||
float broadRangeX = Math.Max(rangeX * 2, 500);
|
||||
float broadRangeY = Math.Max(rangeY * 2, 500);
|
||||
|
||||
if (item.CurrentHull == null && item.Submarine != null && Level.Loaded != null &&
|
||||
if (item.CurrentHull == null && item.Submarine != null &&
|
||||
(Target == TargetType.Wall || Target == TargetType.Any))
|
||||
{
|
||||
if (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity)
|
||||
if (Level.Loaded != null && (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity))
|
||||
{
|
||||
var cells = Level.Loaded.GetCells(item.WorldPosition, 1);
|
||||
foreach (var cell in cells)
|
||||
@@ -268,7 +279,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() < MinimumVelocity * MinimumVelocity) { continue; }
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
@@ -276,23 +287,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
detectOffset.X = -detectOffset.X;
|
||||
}
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
detectOffset.Y = -detectOffset.Y;
|
||||
}
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
Vector2 prevDetectOffset = detectOffset;
|
||||
//undo flipping before saving
|
||||
if (item.FlippedX) { detectOffset.X = -detectOffset.X; }
|
||||
if (item.FlippedY) { detectOffset.Y = -detectOffset.Y; }
|
||||
XElement element = base.Save(parentElement);
|
||||
detectOffset = prevDetectOffset;
|
||||
return element;
|
||||
|
||||
@@ -15,14 +15,14 @@ namespace Barotrauma.Items.Components
|
||||
bool sendOutput = false;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] <= timeFrame) sendOutput = true;
|
||||
if (timeSinceReceived[i] <= timeFrame) { sendOutput = true; }
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
string signalOut = sendOutput ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut)) return;
|
||||
if (string.IsNullOrEmpty(signalOut)) { return; }
|
||||
|
||||
item.SendSignal(signalOut, "signal_out");
|
||||
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class OxygenDetector : ItemComponent
|
||||
{
|
||||
private int prevSentOxygenValue;
|
||||
private string oxygenSignal;
|
||||
|
||||
public OxygenDetector(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
@@ -12,9 +15,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.CurrentHull == null) return;
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
item.SendSignal(((int)item.CurrentHull.OxygenPercentage).ToString(), "signal_out");
|
||||
if (prevSentOxygenValue != (int)item.CurrentHull.OxygenPercentage || oxygenSignal == null)
|
||||
{
|
||||
prevSentOxygenValue = (int)item.CurrentHull.OxygenPercentage;
|
||||
oxygenSignal = prevSentOxygenValue.ToString();
|
||||
}
|
||||
|
||||
item.SendSignal(oxygenSignal, "signal_out");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user