Unstable 0.1500.0.0
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -328,5 +329,8 @@ namespace Barotrauma
|
||||
|
||||
protected virtual void OnStateChanged(AIState from, AIState to) { }
|
||||
protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { }
|
||||
|
||||
public virtual void ClientRead(IReadMessage msg) { }
|
||||
public virtual void ServerWrite(IWriteMessage msg) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -37,6 +38,12 @@ namespace Barotrauma
|
||||
PreviousState = _state;
|
||||
OnStateChanged(_state, value);
|
||||
_state = value;
|
||||
if (_state == AIState.Attack)
|
||||
{
|
||||
#if CLIENT
|
||||
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +57,8 @@ namespace Barotrauma
|
||||
private readonly float updateTargetsInterval = 1;
|
||||
private readonly float updateMemoriesInverval = 1;
|
||||
private readonly float attackLimbResetInterval = 2;
|
||||
// Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target.
|
||||
private const float minPriority = 10;
|
||||
|
||||
private readonly float avoidLookAheadDistance;
|
||||
|
||||
@@ -394,7 +403,7 @@ namespace Barotrauma
|
||||
public void SelectTarget(AITarget target, float priority)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = GetTargetMemory(target, true);
|
||||
selectedTargetMemory = GetTargetMemory(target, addIfNotFound: true);
|
||||
selectedTargetMemory.Priority = priority;
|
||||
ignoredTargets.Remove(target);
|
||||
}
|
||||
@@ -641,7 +650,7 @@ namespace Barotrauma
|
||||
{
|
||||
Character c = a.Character;
|
||||
if (c.IsDead || c.Removed) { return false; }
|
||||
if (!IsFriendly(Character, c)) { return true; }
|
||||
if (!Character.IsFriendly(c)) { return true; }
|
||||
// Only apply the threshold to friendly characters
|
||||
return a.Damage >= selectedTargetingParams.DamageThreshold;
|
||||
}
|
||||
@@ -976,7 +985,7 @@ namespace Barotrauma
|
||||
Character owner = GetOwner(item);
|
||||
if (owner != null)
|
||||
{
|
||||
if (IsFriendly(Character, owner))
|
||||
if (Character.IsFriendly(owner))
|
||||
{
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
@@ -1337,7 +1346,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
canAttack = Character.CharacterList.All(c => c == Character || !IsFriendly(Character, c) || IsFarEnough(c));
|
||||
canAttack = Character.CharacterList.All(c => c == Character || !Character.IsFriendly(c) || IsFarEnough(c));
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
@@ -1356,7 +1365,7 @@ namespace Barotrauma
|
||||
{
|
||||
hitTarget = limb.character;
|
||||
}
|
||||
if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget))
|
||||
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1764,7 +1773,7 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
|
||||
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
bool isFriendly = Character.IsFriendly(attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
State = AIState.Escape;
|
||||
@@ -1840,7 +1849,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true);
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
@@ -1884,16 +1893,15 @@ namespace Barotrauma
|
||||
private bool UpdateLimbAttack(float deltaTime, Limb attackingLimb, Vector2 attackSimPos, float distance = -1, Limb targetLimb = null)
|
||||
{
|
||||
if (SelectedAiTarget?.Entity == null) { return false; }
|
||||
|
||||
ActiveAttack = attackingLimb?.attack;
|
||||
|
||||
if (attackingLimb?.attack == null) { return false; }
|
||||
ActiveAttack = attackingLimb.attack;
|
||||
if (wallTarget != null)
|
||||
{
|
||||
// If the selected target is not the wall target, make the wall target the selected target.
|
||||
var aiTarget = wallTarget.Structure.AiTarget;
|
||||
if (aiTarget != null && SelectedAiTarget != aiTarget)
|
||||
{
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, true).Priority);
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority);
|
||||
State = AIState.Attack;
|
||||
}
|
||||
}
|
||||
@@ -1902,23 +1910,35 @@ namespace Barotrauma
|
||||
{
|
||||
//simulate attack input to get the character to attack client-side
|
||||
Character.SetInput(InputType.Attack, true, true);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
|
||||
if (!ActiveAttack.IsRunning)
|
||||
{
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
|
||||
{
|
||||
Networking.NetEntityEvent.Type.SetAttackTarget,
|
||||
attackingLimb,
|
||||
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
|
||||
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
|
||||
SimPosition.X,
|
||||
SimPosition.Y
|
||||
});
|
||||
});
|
||||
#else
|
||||
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
|
||||
{
|
||||
if (damageTarget.Health > 0 && attackResult.Damage > 0)
|
||||
{
|
||||
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
|
||||
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed;
|
||||
float greed = AIParams.AggressionGreed;
|
||||
if (!(damageTarget is Character))
|
||||
{
|
||||
// Halve the greed for attacking non-characters.
|
||||
greed /= 2;
|
||||
}
|
||||
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2125,6 +2145,9 @@ namespace Barotrauma
|
||||
string targetingTag = null;
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
// ignore if target is tagged to be explicitly ignored (Feign Death)
|
||||
if (targetCharacter.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { continue; }
|
||||
|
||||
if (targetCharacter.IsDead)
|
||||
{
|
||||
targetingTag = "dead";
|
||||
@@ -2139,7 +2162,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsFriendly(Character, targetCharacter))
|
||||
if (Character.IsFriendly(targetCharacter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2449,7 +2472,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (otherCharacter == character) { continue; }
|
||||
if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; }
|
||||
if (!IsFriendly(character, otherCharacter)) { continue; }
|
||||
if (!character.IsFriendly(otherCharacter)) { continue; }
|
||||
valueModifier /= 2;
|
||||
}
|
||||
}
|
||||
@@ -2469,7 +2492,7 @@ namespace Barotrauma
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, true);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
|
||||
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
|
||||
{
|
||||
// Inside the sub, treat objects that are up or down, as they were farther away.
|
||||
@@ -2527,7 +2550,7 @@ namespace Barotrauma
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (owner == character) { continue; }
|
||||
if (owner != null && (IsFriendly(Character, owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
|
||||
if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -2599,7 +2622,7 @@ namespace Barotrauma
|
||||
wall = wallTarget?.Structure;
|
||||
}
|
||||
// The target is not a wall or it's not the same as we are attached to -> release
|
||||
bool releaseTarget = wall == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB);
|
||||
bool releaseTarget = wall?.Bodies == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB);
|
||||
if (!releaseTarget)
|
||||
{
|
||||
for (int i = 0; i < wall.Sections.Length; i++)
|
||||
@@ -2847,10 +2870,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (addIfNotFound)
|
||||
{
|
||||
memory = new AITargetMemory(target, 10);
|
||||
memory = new AITargetMemory(target, minPriority);
|
||||
targetMemories.Add(target, memory);
|
||||
}
|
||||
}
|
||||
if (addIfNotFound)
|
||||
{
|
||||
// Keep the memory alive.
|
||||
memory.Priority = Math.Max(memory.Priority, minPriority);
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
|
||||
@@ -3014,7 +3042,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!onlyExisting && !tempParams.ContainsKey(tag))
|
||||
{
|
||||
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
|
||||
if (AIParams.TryAddNewTarget(tag, state, priority ?? minPriority, out targetParams))
|
||||
{
|
||||
tempParams.Add(tag, targetParams);
|
||||
}
|
||||
@@ -3051,6 +3079,7 @@ namespace Barotrauma
|
||||
ChangeParams(target.SpeciesName, state, priority);
|
||||
if (target.IsHuman)
|
||||
{
|
||||
priority = GetTargetParams("human")?.Priority;
|
||||
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
|
||||
if (state == AIState.Attack || state == AIState.Escape)
|
||||
{
|
||||
@@ -3061,20 +3090,19 @@ namespace Barotrauma
|
||||
{
|
||||
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
|
||||
// --> Target the submarine too.
|
||||
if (target.Submarine != null && (canAttackDoors || canAttackWalls))
|
||||
if (target.Submarine != null && Character.Submarine == null && (canAttackDoors || canAttackWalls))
|
||||
{
|
||||
ChangeParams("room", state, priority);
|
||||
ChangeParams("room", state, priority * 0.1f);
|
||||
if (canAttackWalls)
|
||||
{
|
||||
ChangeParams("wall", state, priority);
|
||||
ChangeParams("wall", state, priority * 0.1f);
|
||||
}
|
||||
if (canAttackDoors)
|
||||
{
|
||||
ChangeParams("door", state, priority);
|
||||
ChangeParams("door", state, priority * 0.1f);
|
||||
}
|
||||
}
|
||||
ChangeParams("provocative", state, priority, onlyExisting: true);
|
||||
ChangeParams("light", state, priority, onlyExisting: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3306,7 +3334,17 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
|
||||
public override void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)State);
|
||||
PetBehavior?.ServerWrite(msg);
|
||||
}
|
||||
|
||||
public override void ClientRead(IReadMessage msg)
|
||||
{
|
||||
State = (AIState)msg.ReadByte();
|
||||
PetBehavior?.ClientRead(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//the "memory" of the Character
|
||||
|
||||
@@ -915,10 +915,10 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void ReportProblem(Character reporter, Order order)
|
||||
public static void ReportProblem(Character reporter, Order order, Hull targetHull = null)
|
||||
{
|
||||
if (reporter == null || order == null) { return; }
|
||||
var visibleHulls = new List<Hull>(reporter.GetVisibleHulls());
|
||||
var visibleHulls = targetHull is null ? new List<Hull>(reporter.GetVisibleHulls()) : new List<Hull> { targetHull };
|
||||
foreach (var hull in visibleHulls)
|
||||
{
|
||||
PropagateHullSafety(reporter, hull);
|
||||
@@ -1415,7 +1415,7 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
{
|
||||
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss;
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
||||
}
|
||||
|
||||
if (accumulatedDamage <= WarningThreshold) { return; }
|
||||
@@ -1510,7 +1510,7 @@ namespace Barotrauma
|
||||
var reputationLoss = MathHelper.Clamp(
|
||||
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
|
||||
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss;
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
||||
}
|
||||
item.StolenDuringRound = true;
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
|
||||
@@ -1971,13 +1971,13 @@ namespace Barotrauma
|
||||
if (c.Removed) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
other = c;
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (c.SelectedConstruction == target.Item)
|
||||
{
|
||||
// If the other character is player, don't try to operate
|
||||
return true;
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is HumanAIController operatingAI)
|
||||
@@ -1991,7 +1991,8 @@ namespace Barotrauma
|
||||
if (!isOrder && isTargetOrdered)
|
||||
{
|
||||
// If the other bot is ordered to operate the item, let him do it, unless we are ordered too
|
||||
return true;
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2012,18 +2013,20 @@ namespace Barotrauma
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
if (Character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"))
|
||||
{
|
||||
return true;
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c))
|
||||
{
|
||||
return true;
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return other != null;
|
||||
bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,12 +161,13 @@ namespace Barotrauma
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
{
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any())
|
||||
if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
|
||||
{
|
||||
//current path calculated relative to a different sub than where the character is now
|
||||
//target in a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.Nodes.First().Submarine;
|
||||
if (currentPathSub != character.Submarine && character.Submarine != null)
|
||||
Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
|
||||
if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
|
||||
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
|
||||
+9
-8
@@ -101,11 +101,11 @@ namespace Barotrauma
|
||||
|
||||
public enum CombatMode
|
||||
{
|
||||
Defensive,
|
||||
Offensive,
|
||||
Arrest,
|
||||
Retreat,
|
||||
None
|
||||
Defensive, // Use weapons against the enemy, but try to retreat to a safe place
|
||||
Offensive, // Engage the enemy and keep attacking it
|
||||
Arrest, // Try to arrest the enemy without using lethal weapons (stunning + handcuffs)
|
||||
Retreat, // Run to a safe place without attacking the target
|
||||
None // Don't use
|
||||
}
|
||||
|
||||
public CombatMode Mode { get; private set; }
|
||||
@@ -958,14 +958,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
|
||||
distanceTimer = distanceCheckInterval;
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
bool closeEnough = true;
|
||||
float sqrRange = meleeWeapon.Range * meleeWeapon.Range;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (sqrDist > sqrRange)
|
||||
if (sqrDistance > sqrRange)
|
||||
{
|
||||
closeEnough = false;
|
||||
}
|
||||
@@ -1003,7 +1004,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (WeaponComponent is RepairTool repairTool)
|
||||
{
|
||||
if (sqrDist > repairTool.Range * repairTool.Range) { return; }
|
||||
if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
|
||||
|
||||
+6
-2
@@ -53,9 +53,13 @@ namespace Barotrauma
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
||||
if (severity > 0.5f && !isOrder)
|
||||
if (severity > 0.75f && !isOrder &&
|
||||
targetHull.RoomName != null &&
|
||||
!targetHull.RoomName.Contains("reactor", StringComparison.OrdinalIgnoreCase) &&
|
||||
!targetHull.RoomName.Contains("engine", StringComparison.OrdinalIgnoreCase) &&
|
||||
!targetHull.RoomName.Contains("command", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Ignore severe fires unless ordered. (Let the fire drain all the oxygen instead).
|
||||
// Ignore severe fires to prevent casualities unless ordered to extinguish.
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// 0-1 based on the horizontal size of all of the fires in the hull.
|
||||
/// </summary>
|
||||
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, Math.Min(hull.Rect.Width, 1000), hull.FireSources.Sum(fs => fs.Size.X)));
|
||||
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 500, hull.FireSources.Sum(fs => fs.Size.X)));
|
||||
|
||||
protected override IEnumerable<Hull> GetList() => Hull.hullList;
|
||||
|
||||
|
||||
+1
@@ -63,6 +63,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 (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-15
@@ -57,7 +57,20 @@ namespace Barotrauma
|
||||
};
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref getDivingGear));
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref getDivingGear);
|
||||
if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
foreach (Item mask in masks)
|
||||
{
|
||||
if (mask != targetItem)
|
||||
{
|
||||
character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -71,9 +84,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: min))
|
||||
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f);
|
||||
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min).Count == 1)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoglastoxygentank"), null, 0.0f, "dialoglastoxygentank", 30.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -105,7 +122,7 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 0.01f))
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f);
|
||||
}
|
||||
@@ -121,7 +138,7 @@ namespace Barotrauma
|
||||
int ReportOxygenTankCount()
|
||||
{
|
||||
if (character.Submarine != Submarine.MainSub) { return 1; }
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("oxygensource") && i.Condition > 1);
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f);
|
||||
@@ -136,17 +153,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns false only when no inventory can be found from the item.
|
||||
/// </summary>
|
||||
public static bool EjectEmptyTanks(Character actor, Item target, out IEnumerable<Item> containedItems)
|
||||
{
|
||||
containedItems = target.OwnInventory?.AllItems;
|
||||
if (containedItems == null) { return false; }
|
||||
AIController.UnequipEmptyItems(actor, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
+7
-20
@@ -242,9 +242,8 @@ namespace Barotrauma
|
||||
if (!searchingNewHull)
|
||||
{
|
||||
//find all available hulls first
|
||||
FindTargetHulls();
|
||||
searchingNewHull = true;
|
||||
return;
|
||||
FindTargetHulls();
|
||||
}
|
||||
else if (targetHulls.Any())
|
||||
{
|
||||
@@ -255,11 +254,10 @@ namespace Barotrauma
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: null, nodeFilter: node =>
|
||||
{
|
||||
if (node.Waypoint.CurrentHull == null) { return false; }
|
||||
// Check that there is no unsafe or forbidden hulls on the way to the target
|
||||
// Check that there is no unsafe hulls on the way to the target
|
||||
if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; }
|
||||
if (isCurrentHullAllowed && IsForbidden(node.Waypoint.CurrentHull)) { return false; }
|
||||
return true;
|
||||
});
|
||||
}, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull));
|
||||
if (path.Unreachable)
|
||||
{
|
||||
//can't go to this room, remove it from the list and try another room
|
||||
@@ -271,30 +269,19 @@ namespace Barotrauma
|
||||
SetTargetTimerLow();
|
||||
return;
|
||||
}
|
||||
character.AIController.SelectTarget(currentTarget.AiTarget);
|
||||
PathSteering.SetPath(path);
|
||||
SetTargetTimerNormal();
|
||||
searchingNewHull = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Couldn't find a target for some reason -> reset
|
||||
// Couldn't find a valid hull
|
||||
SetTargetTimerHigh();
|
||||
searchingNewHull = false;
|
||||
}
|
||||
|
||||
if (currentTarget != null)
|
||||
{
|
||||
character.AIController.SelectTarget(currentTarget.AiTarget);
|
||||
string errorMsg = null;
|
||||
#if DEBUG
|
||||
bool isRoomNameFound = currentTarget.DisplayName != null;
|
||||
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
|
||||
#endif
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
PathSteering.SetPath(path);
|
||||
}
|
||||
SetTargetTimerNormal();
|
||||
}
|
||||
newTargetTimer -= deltaTime;
|
||||
|
||||
if (!character.IsClimbing && IsSteeringFinished())
|
||||
{
|
||||
Wander(deltaTime);
|
||||
|
||||
@@ -141,6 +141,7 @@ namespace Barotrauma
|
||||
public Entity TargetEntity;
|
||||
public ItemComponent TargetItemComponent;
|
||||
public readonly bool UseController;
|
||||
public readonly string[] ControllerTags;
|
||||
public Controller ConnectedController;
|
||||
|
||||
public Character OrderGiver;
|
||||
@@ -309,6 +310,7 @@ namespace Barotrauma
|
||||
color = orderElement.GetAttributeColor("color");
|
||||
FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f);
|
||||
UseController = orderElement.GetAttributeBool("usecontroller", false);
|
||||
ControllerTags = orderElement.GetAttributeStringArray("controllertags", new string[0]);
|
||||
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
|
||||
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
|
||||
Options = orderElement.GetAttributeStringArray("options", new string[0]);
|
||||
@@ -380,6 +382,7 @@ namespace Barotrauma
|
||||
SymbolSprite = prefab.SymbolSprite;
|
||||
Color = prefab.Color;
|
||||
UseController = prefab.UseController;
|
||||
ControllerTags = prefab.ControllerTags;
|
||||
TargetAllCharacters = prefab.TargetAllCharacters;
|
||||
AppropriateJobs = prefab.AppropriateJobs;
|
||||
FadeOutTime = prefab.FadeOutTime;
|
||||
@@ -399,7 +402,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (UseController)
|
||||
{
|
||||
ConnectedController = targetItem.Item?.FindController();
|
||||
ConnectedController = targetItem.Item?.FindController(tags: ControllerTags);
|
||||
if (ConnectedController == null)
|
||||
{
|
||||
DebugConsole.AddWarning("AI: Tried to use a controller for operating an item, but couldn't find any.");
|
||||
@@ -450,19 +453,37 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "")
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", int? priority = null)
|
||||
{
|
||||
orderOption ??= "";
|
||||
|
||||
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + Identifier;
|
||||
if (Identifier != "dismissed" && !string.IsNullOrEmpty(orderOption)) { messageTag += "." + orderOption; }
|
||||
|
||||
if (targetCharacterName == null) { targetCharacterName = ""; }
|
||||
if (targetRoomName == null) { targetRoomName = ""; }
|
||||
string msg = TextManager.GetWithVariables(messageTag, new string[2] { "[name]", "[roomname]" }, new string[2] { targetCharacterName, targetRoomName }, new bool[2] { false, true }, true);
|
||||
if (msg == null) { return ""; }
|
||||
|
||||
return msg;
|
||||
priority ??= CharacterInfo.HighestManualOrderPriority;
|
||||
// If the order has a lesser priority, it means we are rearranging character orders
|
||||
if (!TargetAllCharacters && priority != CharacterInfo.HighestManualOrderPriority && Identifier != "dismissed")
|
||||
{
|
||||
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
|
||||
}
|
||||
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}";
|
||||
messageTag += $".{Identifier}";
|
||||
if (!string.IsNullOrEmpty(orderOption))
|
||||
{
|
||||
if (Identifier != "dismissed")
|
||||
{
|
||||
messageTag += $".{orderOption}";
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] splitOption = orderOption.Split('.');
|
||||
if (splitOption.Length > 0)
|
||||
{
|
||||
messageTag += $".{splitOption[0]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
string msg = TextManager.GetWithVariables(messageTag,
|
||||
new string[2] { "[name]", "[roomname]" },
|
||||
new string[2] { targetCharacterName ?? string.Empty, targetRoomName ?? string.Empty },
|
||||
formatCapitals: new bool[2] { false, true },
|
||||
returnNull: true);
|
||||
return msg ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -505,7 +526,7 @@ namespace Barotrauma
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; }
|
||||
Controller controller = null;
|
||||
if (UseController && !item.TryFindController(out controller)) { continue; }
|
||||
if (UseController && !item.TryFindController(out controller, tags: ControllerTags)) { continue; }
|
||||
if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; }
|
||||
matchingItems.Add(item);
|
||||
}
|
||||
|
||||
@@ -97,64 +97,51 @@ namespace Barotrauma
|
||||
|
||||
foreach (WayPoint wp in wayPoints)
|
||||
{
|
||||
wp.linkedTo.CollectionChanged += WaypointLinksChanged;
|
||||
wp.OnLinksChanged += WaypointLinksChanged;
|
||||
}
|
||||
|
||||
IndoorsSteering = indoorsSteering;
|
||||
}
|
||||
|
||||
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
void WaypointLinksChanged(WayPoint wp)
|
||||
{
|
||||
if (Submarine.Unloading) { return; }
|
||||
|
||||
var waypoints = sender as IEnumerable<MapEntity>;
|
||||
var node = nodes.Find(n => n.Waypoint == wp);
|
||||
if (node == null) { return; }
|
||||
|
||||
foreach (MapEntity me in waypoints)
|
||||
for (int i = node.connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
WayPoint wp = me as WayPoint;
|
||||
if (me == null) { continue; }
|
||||
|
||||
var node = nodes.Find(n => n.Waypoint == wp);
|
||||
if (node == null) { return; }
|
||||
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
|
||||
//remove connection if the waypoint isn't connected anymore
|
||||
if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null)
|
||||
{
|
||||
for (int i = node.connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
//remove connection if the waypoint isn't connected anymore
|
||||
if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null)
|
||||
{
|
||||
node.connections.RemoveAt(i);
|
||||
node.distances.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
node.connections.RemoveAt(i);
|
||||
node.distances.RemoveAt(i);
|
||||
}
|
||||
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
|
||||
}
|
||||
|
||||
for (int i = 0; i < wp.linkedTo.Count; i++)
|
||||
{
|
||||
if (!(wp.linkedTo[i] is WayPoint connected)) { continue; }
|
||||
|
||||
//already connected, continue
|
||||
if (node.connections.Any(n => n.Waypoint == connected)) { continue; }
|
||||
|
||||
var matchingNode = nodes.Find(n => n.Waypoint == connected);
|
||||
if (matchingNode == null)
|
||||
{
|
||||
for (int i = 0; i < wp.linkedTo.Count; i++)
|
||||
{
|
||||
if (!(wp.linkedTo[i] is WayPoint connected)) { continue; }
|
||||
|
||||
//already connected, continue
|
||||
if (node.connections.Any(n => n.Waypoint == connected)) { continue; }
|
||||
|
||||
var matchingNode = nodes.Find(n => n.Waypoint == connected);
|
||||
if (matchingNode == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder");
|
||||
DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
node.connections.Add(matchingNode);
|
||||
node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
node.connections.Add(matchingNode);
|
||||
node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly List<PathNode> sortedNodes = new List<PathNode>();
|
||||
private readonly List<PathNode> sortedNodes = new List<PathNode>();
|
||||
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
{
|
||||
|
||||
@@ -219,6 +219,7 @@ namespace Barotrauma
|
||||
if (character.SelectedCharacter != null)
|
||||
{
|
||||
DragCharacter(character.SelectedCharacter, deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
//don't flip when simply physics is enabled
|
||||
|
||||
+35
-9
@@ -938,19 +938,23 @@ namespace Barotrauma
|
||||
|
||||
float rotation = MathHelper.WrapAngle(Collider.Rotation);
|
||||
rotation = MathHelper.ToDegrees(rotation);
|
||||
if (rotation < 0.0f) rotation += 360;
|
||||
|
||||
if (rotation < 0.0f)
|
||||
{
|
||||
rotation += 360;
|
||||
}
|
||||
if (!character.IsRemotelyControlled && !aiming && Anim != Animation.UsingConstruction &&
|
||||
!(character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
|
||||
{
|
||||
if (rotation > 20 && rotation < 170)
|
||||
{
|
||||
TargetDir = Direction.Left;
|
||||
}
|
||||
else if (rotation > 190 && rotation < 340)
|
||||
{
|
||||
TargetDir = Direction.Right;
|
||||
}
|
||||
}
|
||||
|
||||
float targetSpeed = TargetMovement.Length();
|
||||
|
||||
if (targetSpeed > 0.1f)
|
||||
{
|
||||
if (!aiming)
|
||||
@@ -965,9 +969,7 @@ 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, 5.0f * character.SpeedMultiplier);
|
||||
}
|
||||
@@ -1622,7 +1624,10 @@ namespace Barotrauma
|
||||
{
|
||||
Vector2 pullLimbAnchor = targetLimb.SimPosition;
|
||||
pullLimb.PullJointMaxForce = 5000.0f;
|
||||
targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
|
||||
if (!character.HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging))
|
||||
{
|
||||
targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY;
|
||||
@@ -1679,7 +1684,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//limit movement if moving away from the target
|
||||
if (Vector2.Dot(target.WorldPosition - WorldPosition, targetMovement) < 0)
|
||||
if (!character.HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging) && Vector2.Dot(target.WorldPosition - WorldPosition, targetMovement) < 0)
|
||||
{
|
||||
targetMovement *= MathHelper.Clamp(1.5f - dist, 0.0f, 1.0f);
|
||||
}
|
||||
@@ -1750,8 +1755,9 @@ namespace Barotrauma
|
||||
Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX;
|
||||
|
||||
holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torso.body.Rotation * Dir;
|
||||
holdAngle += GetAimWobble(rightHand, leftHand, item);
|
||||
|
||||
itemAngle = (torso.body.Rotation + holdAngle * Dir);
|
||||
itemAngle = torso.body.Rotation + holdAngle * Dir;
|
||||
|
||||
if (holdable.ControlPose)
|
||||
{
|
||||
@@ -1869,6 +1875,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float GetAimWobble(Limb rightHand, Limb leftHand, Item heldItem)
|
||||
{
|
||||
float wobbleStrength = 0.0f;
|
||||
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem)
|
||||
{
|
||||
wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: "damage");
|
||||
}
|
||||
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem)
|
||||
{
|
||||
wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: "damage");
|
||||
}
|
||||
if (wobbleStrength <= 0.1f) { return 0.0f; }
|
||||
wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f);
|
||||
|
||||
float lowFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 320.0f, (float)Timing.TotalTime / 240.0f) - 0.5f;
|
||||
float highFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 40.0f, (float)Timing.TotalTime / 50.0f) - 0.5f;
|
||||
|
||||
return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength;
|
||||
}
|
||||
|
||||
private void HandIK(Limb hand, Vector2 pos, float force = 1.0f)
|
||||
{
|
||||
Vector2 shoulderPos;
|
||||
|
||||
@@ -74,6 +74,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
class AttackData
|
||||
{
|
||||
public float DamageMultiplier { get; set; } = 1f;
|
||||
public float AddedPenetration { get; set; } = 0f;
|
||||
public List<Affliction> Afflictions { get; set; }
|
||||
public Attack SourceAttack { get; }
|
||||
|
||||
public AttackData(Attack sourceAttack)
|
||||
{
|
||||
SourceAttack = sourceAttack;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial class Attack : ISerializableEntity
|
||||
{
|
||||
[Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
|
||||
@@ -271,6 +285,9 @@ namespace Barotrauma
|
||||
statusEffect.SetUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
// used for talents/ability conditions
|
||||
public Item SourceItem { get; }
|
||||
|
||||
public List<Affliction> GetMultipliedAfflictions(float multiplier)
|
||||
{
|
||||
@@ -320,6 +337,10 @@ namespace Barotrauma
|
||||
Penetration = Penetration;
|
||||
}
|
||||
|
||||
public Attack(XElement element, string parentDebugName, Item sourceItem) : this(element, parentDebugName)
|
||||
{
|
||||
SourceItem = sourceItem;
|
||||
}
|
||||
public Attack(XElement element, string parentDebugName)
|
||||
{
|
||||
Deserialize(element);
|
||||
|
||||
@@ -461,10 +461,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowInput
|
||||
{
|
||||
get { return Stun <= 0.0f && !IsDead && !IsIncapacitated; }
|
||||
}
|
||||
public bool AllowInput => !Removed && !IsIncapacitated && Stun <= 0.0f;
|
||||
|
||||
public bool CanMove
|
||||
{
|
||||
@@ -476,10 +473,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanInteract
|
||||
{
|
||||
get { return AllowInput && IsHumanoid && !LockHands && !Removed && !IsIncapacitated; }
|
||||
}
|
||||
public bool CanInteract => AllowInput && IsHumanoid && !LockHands;
|
||||
|
||||
// Eating is not implemented for humanoids. If we implement that at some point, we could remove this restriction.
|
||||
public bool CanEat => !IsHumanoid && Params.CanEat && AllowInput && AnimController.GetLimb(LimbType.Head) != null;
|
||||
|
||||
public Vector2 CursorPosition
|
||||
{
|
||||
@@ -773,8 +770,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsObserving => AIController is EnemyAIController enemyAI && enemyAI.Enabled && enemyAI.State == AIState.Observe;
|
||||
|
||||
public bool EnableDespawn { get; set; } = true;
|
||||
|
||||
public CauseOfDeath CauseOfDeath
|
||||
@@ -1378,6 +1373,12 @@ namespace Barotrauma
|
||||
if (Info?.Job == null) { return 0.0f; }
|
||||
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
|
||||
|
||||
// apply multipliers first so that multipliers only affect base skill value
|
||||
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
|
||||
{
|
||||
skillLevel *= affliction.GetSkillMultiplier();
|
||||
}
|
||||
|
||||
if (skillIdentifier != null)
|
||||
{
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
@@ -1392,10 +1393,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
|
||||
{
|
||||
skillLevel *= affliction.GetSkillMultiplier();
|
||||
}
|
||||
skillLevel += GetStatValue(GetSkillStatType(skillIdentifier));
|
||||
|
||||
return skillLevel;
|
||||
}
|
||||
|
||||
@@ -1432,9 +1431,9 @@ namespace Barotrauma
|
||||
// - dragging someone
|
||||
// - crouching
|
||||
// - moving backwards
|
||||
public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
|
||||
public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged || HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging)) &&
|
||||
(!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
|
||||
!AnimController.IsMovingBackwards;
|
||||
!AnimController.IsMovingBackwards && !HasAbilityFlag(AbilityFlags.MustWalk);
|
||||
|
||||
public Vector2 ApplyMovementLimits(Vector2 targetMovement, float currentSpeed)
|
||||
{
|
||||
@@ -1595,7 +1594,7 @@ namespace Barotrauma
|
||||
return Math.Clamp(reduction, 0, 1f);
|
||||
}
|
||||
|
||||
private float CalculateMovementPenalty(Limb limb, float sum, float max = 0.4f)
|
||||
private float CalculateMovementPenalty(Limb limb, float sum, float max = 0.8f)
|
||||
{
|
||||
if (limb != null)
|
||||
{
|
||||
@@ -1628,7 +1627,7 @@ namespace Barotrauma
|
||||
float max;
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
max = AnimController.InWater ? 0.5f : 0.7f;
|
||||
max = AnimController.InWater ? 0.5f : 0.8f;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2044,7 +2043,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public Item GetEquippedItem(string tagOrIdentifier, InvSlotType? slotType = null)
|
||||
public Item GetEquippedItem(string tagOrIdentifier = null, InvSlotType? slotType = null)
|
||||
{
|
||||
if (Inventory == null) { return null; }
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
@@ -2059,7 +2058,7 @@ namespace Barotrauma
|
||||
}
|
||||
var item = Inventory.GetItemAt(i);
|
||||
if (item == null) { continue; }
|
||||
if (item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; }
|
||||
if (tagOrIdentifier == null || item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -2365,9 +2364,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this))
|
||||
{
|
||||
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
|
||||
if ((findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) && (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld))
|
||||
{
|
||||
FocusedCharacter = CanInteract ? FindCharacterAtPosition(mouseSimPos) : null;
|
||||
FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null;
|
||||
if (FocusedCharacter != null && !CanSeeCharacter(FocusedCharacter)) { FocusedCharacter = null; }
|
||||
float aimAssist = GameMain.Config.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f);
|
||||
if (HeldItems.Any(it => it?.GetComponent<Wire>()?.IsActive ?? false))
|
||||
@@ -2445,7 +2444,7 @@ namespace Barotrauma
|
||||
{
|
||||
DeselectCharacter();
|
||||
}
|
||||
else if (FocusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && CanInteract)
|
||||
else if (FocusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && (CanInteract || FocusedCharacter.IsDead && CanEat))
|
||||
{
|
||||
SelectCharacter(FocusedCharacter);
|
||||
}
|
||||
@@ -2624,6 +2623,11 @@ namespace Barotrauma
|
||||
|
||||
UpdateAttackers(deltaTime);
|
||||
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.UpdateTalent(deltaTime);
|
||||
}
|
||||
|
||||
if (IsDead) { return; }
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
@@ -2712,6 +2716,9 @@ namespace Barotrauma
|
||||
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
|
||||
bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true;
|
||||
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 5.0f * 5.0f;
|
||||
bool wasRagdolled = false;
|
||||
bool selfRagdolled = false;
|
||||
|
||||
if (IsForceRagdolled)
|
||||
{
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
@@ -2730,12 +2737,17 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
bool wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasRagdolled && IsRagdolled && selfRagdolled)
|
||||
{
|
||||
CheckTalents(AbilityEffectType.OnSelfRagdoll);
|
||||
}
|
||||
|
||||
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
|
||||
|
||||
//ragdoll button
|
||||
@@ -3092,6 +3104,12 @@ namespace Barotrauma
|
||||
|
||||
OrderInfo newOrderInfo = new OrderInfo(order, orderOption, priority);
|
||||
AddCurrentOrder(newOrderInfo);
|
||||
|
||||
if (orderGiver != null)
|
||||
{
|
||||
orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, this);
|
||||
}
|
||||
|
||||
if (AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.SetOrder(order, orderOption, priority, orderGiver, speak);
|
||||
@@ -3337,9 +3355,25 @@ namespace Barotrauma
|
||||
|
||||
float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime;
|
||||
|
||||
AttackData attackData = new AttackData(attack);
|
||||
attacker.CheckTalents(AbilityEffectType.OnAttack, attackData);
|
||||
CheckTalents(AbilityEffectType.OnAttacked, attackData);
|
||||
attackData.DamageMultiplier *= (1 + attacker.GetStatValue(StatTypes.AttackMultiplier));
|
||||
|
||||
IEnumerable<Affliction> attackAfflictions;
|
||||
|
||||
if (attackData.Afflictions != null)
|
||||
{
|
||||
attackAfflictions = attackData.Afflictions.Union(attack.Afflictions.Keys);
|
||||
}
|
||||
else
|
||||
{
|
||||
attackAfflictions = attack.Afflictions.Keys;
|
||||
}
|
||||
|
||||
var attackResult = targetLimb == null ?
|
||||
AddDamage(worldPosition, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier) :
|
||||
DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier, penetration: penetration);
|
||||
AddDamage(worldPosition, attackAfflictions, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier * attackData.DamageMultiplier) :
|
||||
DamageLimb(worldPosition, targetLimb, attackAfflictions, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier * attackData.DamageMultiplier, penetration: penetration + attackData.AddedPenetration);
|
||||
|
||||
if (limbHit == null) { return new AttackResult(); }
|
||||
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
|
||||
@@ -3457,6 +3491,11 @@ namespace Barotrauma
|
||||
|
||||
public void RecordKill(Character target)
|
||||
{
|
||||
foreach (Character attackerCrewmember in GetFriendlyCrew(this))
|
||||
{
|
||||
attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target);
|
||||
}
|
||||
|
||||
if (!IsOnPlayerTeam) { return; }
|
||||
if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; }
|
||||
GameMain.Config.KilledCreatures.Add(target.SpeciesName);
|
||||
@@ -3524,7 +3563,7 @@ namespace Barotrauma
|
||||
bool wasDead = IsDead;
|
||||
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
|
||||
float prevVitality = CharacterHealth.Vitality;
|
||||
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration);
|
||||
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration, attacker: attacker);
|
||||
CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking);
|
||||
if (attacker != this)
|
||||
{
|
||||
@@ -3551,6 +3590,9 @@ namespace Barotrauma
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
}
|
||||
|
||||
attacker?.CheckTalents(AbilityEffectType.OnAttackResult, attackResult);
|
||||
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
@@ -3775,6 +3817,8 @@ namespace Barotrauma
|
||||
causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource);
|
||||
OnDeath?.Invoke(this, CauseOfDeath);
|
||||
|
||||
CheckTalents(AbilityEffectType.OnDieToCharacter, CauseOfDeath.Killer);
|
||||
|
||||
if (GameMain.GameSession != null && Screen.Selected == GameMain.GameScreen)
|
||||
{
|
||||
SteamAchievementManager.OnCharacterKilled(this, CauseOfDeath);
|
||||
@@ -3782,7 +3826,11 @@ namespace Barotrauma
|
||||
|
||||
KillProjSpecific(causeOfDeath, causeOfDeathAffliction, log);
|
||||
|
||||
if (info != null) { info.CauseOfDeath = CauseOfDeath; }
|
||||
if (info != null)
|
||||
{
|
||||
info.CauseOfDeath = CauseOfDeath;
|
||||
info.ResetSavedStatValues();
|
||||
}
|
||||
AnimController.movement = Vector2.Zero;
|
||||
AnimController.TargetMovement = Vector2.Zero;
|
||||
|
||||
@@ -3805,6 +3853,11 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
if (GameMain.GameSession.Campaign != null && TeamID == CharacterTeamType.Team1 && !IsAssistant)
|
||||
{
|
||||
GameMain.GameSession.Campaign.CrewHasDied = true;
|
||||
}
|
||||
|
||||
GameMain.GameSession.KillCharacter(this);
|
||||
}
|
||||
}
|
||||
@@ -4203,8 +4256,249 @@ namespace Barotrauma
|
||||
|
||||
public bool IsProtectedFromPressure()
|
||||
{
|
||||
return PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
|
||||
return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
|
||||
}
|
||||
|
||||
// Talent logic begins here. Should be encapsulated to its own controller soon
|
||||
|
||||
private readonly List<CharacterTalent> characterTalents = new List<CharacterTalent>();
|
||||
|
||||
public void LoadTalents()
|
||||
{
|
||||
List<string> toBeRemoved = null;
|
||||
foreach (string talent in info.UnlockedTalents)
|
||||
{
|
||||
if (!GiveTalent(talent, addingFirstTime: false))
|
||||
{
|
||||
DebugConsole.AddWarning(Name + " had talent that did not exist! Removing talent from CharacterInfo.");
|
||||
toBeRemoved ??= new List<string>();
|
||||
toBeRemoved.Add(talent);
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeRemoved != null)
|
||||
{
|
||||
foreach (string removeTalent in toBeRemoved)
|
||||
{
|
||||
Info.UnlockedTalents.Remove(removeTalent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool GiveTalent(string talentIdentifier, bool addingFirstTime = true)
|
||||
{
|
||||
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talentIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (talentPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Tried to add talent by identifier {talentIdentifier} to character {Name}, but no such talent exists.");
|
||||
return false;
|
||||
}
|
||||
return GiveTalent(talentPrefab, addingFirstTime);
|
||||
}
|
||||
|
||||
public bool GiveTalent(UInt32 talentIdentifier, bool addingFirstTime = true)
|
||||
{
|
||||
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.UIntIdentifier == talentIdentifier);
|
||||
if (talentPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Tried to add talent by identifier {talentIdentifier} to character {Name}, but no such talent exists.");
|
||||
return false;
|
||||
}
|
||||
return GiveTalent(talentPrefab, addingFirstTime);
|
||||
}
|
||||
|
||||
private bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true)
|
||||
{
|
||||
if (addingFirstTime)
|
||||
{
|
||||
if (!info.UnlockedTalents.Add(talentPrefab.Identifier)) { return false; }
|
||||
}
|
||||
|
||||
DebugConsole.AddWarning("added " + talentPrefab.OriginalName);
|
||||
CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this);
|
||||
characterTalent.ActivateTalent(addingFirstTime);
|
||||
characterTalents.Add(characterTalent);
|
||||
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasTalent(string identifier)
|
||||
{
|
||||
return info.UnlockedTalents.Contains(identifier);
|
||||
}
|
||||
|
||||
public static IEnumerable<Character> GetFriendlyCrew(Character character)
|
||||
{
|
||||
return CharacterList.Where(c => HumanAIController.IsFriendly(character, c, onlySameTeam: true) && !c.IsDead);
|
||||
}
|
||||
|
||||
public void CheckTalents(AbilityEffectType abilityEffectType, object abilityData)
|
||||
{
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.CheckTalent(abilityEffectType, abilityData);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckTalents(AbilityEffectType abilityEffectType)
|
||||
{
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.CheckTalent(abilityEffectType, (object)null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRecipeForItem(string recipeIdentifier)
|
||||
{
|
||||
return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows visual notification of money gained by the specific player. Useful for mid-mission monetary gains.
|
||||
/// </summary>
|
||||
public void GiveMoney(int amount)
|
||||
{
|
||||
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
|
||||
if (amount <= 0) { return; }
|
||||
|
||||
int prevAmount = campaign.Money;
|
||||
campaign.Money += amount;
|
||||
OnMoneyChanged(prevAmount, campaign.Money);
|
||||
}
|
||||
|
||||
public void SetMoney(int amount)
|
||||
{
|
||||
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
|
||||
if (amount == campaign.Money) { return; }
|
||||
|
||||
int prevAmount = campaign.Money;
|
||||
campaign.Money = amount;
|
||||
OnMoneyChanged(prevAmount, campaign.Money);
|
||||
}
|
||||
|
||||
partial void OnMoneyChanged(int prevAmount, int newAmount);
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary is used for stats that are required very frequently. Not very performant, but easier to develop with for now.
|
||||
/// If necessary, the approach of using a dictionary could be replaced by an encapsulated class that contains the stats as attributes.
|
||||
/// </summary>
|
||||
private readonly Dictionary<StatTypes, float> statValues = new Dictionary<StatTypes, float>();
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
{
|
||||
if (!IsHuman) { return 0f; }
|
||||
|
||||
float statValue = 0f;
|
||||
if (statValues.TryGetValue(statType, out float value))
|
||||
{
|
||||
statValue += value;
|
||||
}
|
||||
if (CharacterHealth != null)
|
||||
{
|
||||
statValue += CharacterHealth.GetStatValue(statType);
|
||||
}
|
||||
if (Info != null)
|
||||
{
|
||||
// could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change
|
||||
statValue += Info.GetSavedStatValue(statType);
|
||||
}
|
||||
|
||||
//replace by updating the character wearable stat values when equipping or unequipping wearables
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
{
|
||||
if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.GetItemAt(i)?.GetComponent<Wearable>() is Wearable wearable)
|
||||
{
|
||||
if (wearable.WearableStatValues.TryGetValue(statType, out float wearableValue))
|
||||
{
|
||||
statValue += wearableValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statValue;
|
||||
}
|
||||
|
||||
public void ChangeStat(StatTypes statType, float value)
|
||||
{
|
||||
if (statValues.ContainsKey(statType))
|
||||
{
|
||||
statValues[statType] += value;
|
||||
}
|
||||
else
|
||||
{
|
||||
statValues.Add(statType, value);
|
||||
}
|
||||
}
|
||||
|
||||
private StatTypes GetSkillStatType(string skillIdentifier)
|
||||
{
|
||||
// Using this method to translate between skill identifiers and stat types. Feel free to replace it if there's a better way
|
||||
switch (skillIdentifier)
|
||||
{
|
||||
case "electrical":
|
||||
return StatTypes.ElectricalSkillBonus;
|
||||
case "helm":
|
||||
return StatTypes.HelmSkillBonus;
|
||||
case "mechanical":
|
||||
return StatTypes.MechanicalSkillBonus;
|
||||
case "medical":
|
||||
return StatTypes.MedicalSkillBonus;
|
||||
case "weapons":
|
||||
return StatTypes.WeaponsSkillBonus;
|
||||
default:
|
||||
return StatTypes.None;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<AbilityFlags> abilityFlags = new List<AbilityFlags>();
|
||||
|
||||
public void AddAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
abilityFlags.Add(abilityFlag);
|
||||
}
|
||||
|
||||
public void RemoveAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
abilityFlags.Remove(abilityFlag);
|
||||
}
|
||||
|
||||
public bool HasAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
return abilityFlags.Contains(abilityFlag);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, float> abilityResistances = new Dictionary<string, float>();
|
||||
|
||||
public float GetAbilityResistance(string resistanceId)
|
||||
{
|
||||
return abilityResistances.TryGetValue(resistanceId, out float value) ? value : 1f;
|
||||
}
|
||||
|
||||
public void ChangeAbilityResistance(string resistanceId, float value)
|
||||
{
|
||||
if (abilityResistances.ContainsKey(resistanceId))
|
||||
{
|
||||
abilityResistances[resistanceId] *= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
abilityResistances.Add(resistanceId, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
|
||||
/// </summary>
|
||||
public bool IsFriendly(Character other) => IsFriendly(this, other);
|
||||
|
||||
/// <summary>
|
||||
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
|
||||
/// </summary>
|
||||
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
|
||||
}
|
||||
|
||||
class ActiveTeamChange
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -215,6 +216,12 @@ namespace Barotrauma
|
||||
|
||||
public int Salary;
|
||||
|
||||
public int ExperiencePoints { get; private set; }
|
||||
|
||||
public HashSet<string> UnlockedTalents { get; private set; } = new HashSet<string>();
|
||||
|
||||
public int AdditionalTalentPoints { get; private set; }
|
||||
|
||||
private Sprite headSprite;
|
||||
public Sprite HeadSprite
|
||||
{
|
||||
@@ -529,6 +536,8 @@ namespace Barotrauma
|
||||
OriginalName = infoElement.GetAttributeString("originalname", null);
|
||||
string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
Salary = infoElement.GetAttributeInt("salary", 1000);
|
||||
ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0);
|
||||
UnlockedTalents = new HashSet<string>(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true));
|
||||
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race);
|
||||
Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender);
|
||||
_speciesName = infoElement.GetAttributeString("speciesname", null);
|
||||
@@ -599,10 +608,38 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (XElement subElement in infoElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase))
|
||||
bool jobCreated = false;
|
||||
if (subElement.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase) && !jobCreated)
|
||||
{
|
||||
Job = new Job(subElement);
|
||||
break;
|
||||
jobCreated = true;
|
||||
// there used to be a break here, but it had to be removed to make room for statvalues
|
||||
// using the jobCreated boolean to make sure that only the first job found is created
|
||||
}
|
||||
else if (subElement.Name.ToString().Equals("savedstatvalues", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (XElement savedStat in subElement.Elements())
|
||||
{
|
||||
string statTypeString = savedStat.GetAttributeString("stattype", "").ToLowerInvariant();
|
||||
if (!Enum.TryParse(statTypeString, true, out StatTypes statType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" when loading character data in CharacterInfo!");
|
||||
continue;
|
||||
}
|
||||
|
||||
float value = savedStat.GetAttributeFloat("statvalue", 0f);
|
||||
if (value == 0f) { continue; }
|
||||
|
||||
string statIdentifier = savedStat.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(statIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("Stat identifier not specified for Stat Value when loading character data in CharacterInfo!");
|
||||
return;
|
||||
}
|
||||
|
||||
bool removeOnDeath = savedStat.GetAttributeBool("removeondeath", true);
|
||||
ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadHeadAttachments();
|
||||
@@ -939,6 +976,15 @@ namespace Barotrauma
|
||||
Job.IncreaseSkillLevel(skillIdentifier, increase);
|
||||
|
||||
float newLevel = Job.GetSkillLevel(skillIdentifier);
|
||||
if ((int)newLevel > (int)prevLevel)
|
||||
{
|
||||
Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier);
|
||||
|
||||
foreach (Character character in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, (skillIdentifier, Character));
|
||||
}
|
||||
}
|
||||
|
||||
OnSkillChanged(skillIdentifier, prevLevel, newLevel, pos);
|
||||
}
|
||||
@@ -963,6 +1009,90 @@ namespace Barotrauma
|
||||
|
||||
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos);
|
||||
|
||||
public void GiveExperience(int amount, float popupOffset = 0f, bool isMissionExperience = false)
|
||||
{
|
||||
int prevAmount = ExperiencePoints;
|
||||
|
||||
var experienceGainMultiplier = new AbilityValue(1f);
|
||||
if (isMissionExperience)
|
||||
{
|
||||
Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
|
||||
}
|
||||
experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier);
|
||||
|
||||
amount = (int)(amount * experienceGainMultiplier.Value);
|
||||
|
||||
if (amount < 0) { return; }
|
||||
|
||||
ExperiencePoints += amount;
|
||||
OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * (150.0f + popupOffset));
|
||||
}
|
||||
|
||||
public void SetExperience(int newExperience)
|
||||
{
|
||||
if (newExperience < 0) { return; }
|
||||
|
||||
int prevAmount = ExperiencePoints;
|
||||
ExperiencePoints = newExperience;
|
||||
OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
|
||||
const int BaseExperienceRequired = 150;
|
||||
const int AddedExperienceRequiredPerLevel = 350;
|
||||
|
||||
public int GetTotalTalentPoints()
|
||||
{
|
||||
return GetCurrentLevel() + AdditionalTalentPoints - 1;
|
||||
}
|
||||
|
||||
public int GetAvailableTalentPoints()
|
||||
{
|
||||
// hashset always has at least 1
|
||||
return Math.Max(GetTotalTalentPoints() - UnlockedTalents.Count, 0);
|
||||
}
|
||||
|
||||
public float GetProgressTowardsNextLevel()
|
||||
{
|
||||
float progress = (ExperiencePoints - GetExperienceRequiredForCurrentLevel()) / (GetExperienceRequiredToLevelUp() - GetExperienceRequiredForCurrentLevel());
|
||||
return progress;
|
||||
}
|
||||
|
||||
public float GetExperienceRequiredForCurrentLevel()
|
||||
{
|
||||
GetCurrentLevel(out int experienceRequired);
|
||||
return experienceRequired;
|
||||
}
|
||||
|
||||
public float GetExperienceRequiredToLevelUp()
|
||||
{
|
||||
int level = GetCurrentLevel(out int experienceRequired);
|
||||
return experienceRequired + ExperienceRequiredPerLevel(level);
|
||||
}
|
||||
|
||||
public int GetCurrentLevel()
|
||||
{
|
||||
return GetCurrentLevel(out _);
|
||||
}
|
||||
|
||||
private int GetCurrentLevel(out int experienceRequired)
|
||||
{
|
||||
int level = 1;
|
||||
experienceRequired = 0;
|
||||
while (experienceRequired + ExperienceRequiredPerLevel(level) <= ExperiencePoints)
|
||||
{
|
||||
experienceRequired += ExperienceRequiredPerLevel(level);
|
||||
level++;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
private int ExperienceRequiredPerLevel(int level)
|
||||
{
|
||||
return BaseExperienceRequired + AddedExperienceRequiredPerLevel * level;
|
||||
}
|
||||
|
||||
partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos);
|
||||
|
||||
public void Rename(string newName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newName)) { return; }
|
||||
@@ -999,6 +1129,8 @@ namespace Barotrauma
|
||||
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"),
|
||||
new XAttribute("race", Head.race.ToString()),
|
||||
new XAttribute("salary", Salary),
|
||||
new XAttribute("experiencepoints", ExperiencePoints),
|
||||
new XAttribute("unlockedtalents", string.Join(",", UnlockedTalents)),
|
||||
new XAttribute("headspriteid", HeadSpriteId),
|
||||
new XAttribute("hairindex", HairIndex),
|
||||
new XAttribute("beardindex", BeardIndex),
|
||||
@@ -1020,6 +1152,24 @@ namespace Barotrauma
|
||||
|
||||
Job.Save(charElement);
|
||||
|
||||
XElement savedStatElement = new XElement("savedstatvalues");
|
||||
foreach (var statValuePair in savedStatValues)
|
||||
{
|
||||
foreach (var savedStat in statValuePair.Value)
|
||||
{
|
||||
if (savedStat.StatValue == 0f) { continue; }
|
||||
|
||||
savedStatElement.Add(new XElement("savedstatvalue",
|
||||
new XAttribute("stattype", statValuePair.Key.ToString()),
|
||||
new XAttribute("statidentifier", savedStat.StatIdentifier),
|
||||
new XAttribute("statvalue", savedStat.StatValue),
|
||||
new XAttribute("removeondeath", savedStat.RemoveOnDeath)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
charElement.Add(savedStatElement);
|
||||
|
||||
parentElement.Add(charElement);
|
||||
return charElement;
|
||||
}
|
||||
@@ -1332,5 +1482,68 @@ namespace Barotrauma
|
||||
Portrait = null;
|
||||
AttachmentSprites = null;
|
||||
}
|
||||
|
||||
// This could maybe be a LookUp instead?
|
||||
private readonly Dictionary<StatTypes, List<SavedStatValue>> savedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>();
|
||||
|
||||
public void ResetSavedStatValues()
|
||||
{
|
||||
foreach (var savedStatValue in savedStatValues.SelectMany(s => s.Value))
|
||||
{
|
||||
if (savedStatValue.RemoveOnDeath)
|
||||
{
|
||||
savedStatValue.StatValue = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetSavedStatValue(string statIdentifier)
|
||||
{
|
||||
savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f);
|
||||
}
|
||||
|
||||
public float GetSavedStatValue(StatTypes statType)
|
||||
{
|
||||
if (savedStatValues.TryGetValue(statType, out var statValues))
|
||||
{
|
||||
return statValues.Sum(v => v.StatValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath)
|
||||
{
|
||||
if (!savedStatValues.ContainsKey(statType))
|
||||
{
|
||||
savedStatValues.Add(statType, new List<SavedStatValue>());
|
||||
}
|
||||
|
||||
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
|
||||
{
|
||||
savedStat.StatValue += value;
|
||||
savedStat.RemoveOnDeath = removeOnDeath;
|
||||
}
|
||||
else
|
||||
{
|
||||
savedStatValues[statType].Add(new SavedStatValue(statIdentifier, value, removeOnDeath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SavedStatValue
|
||||
{
|
||||
public string StatIdentifier { get; set; }
|
||||
public float StatValue { get; set; }
|
||||
public bool RemoveOnDeath { get; set; }
|
||||
|
||||
public SavedStatValue(string statIdentifier, float value, bool removeOnDeath)
|
||||
{
|
||||
StatValue = value;
|
||||
RemoveOnDeath = removeOnDeath;
|
||||
StatIdentifier = statIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-1
@@ -237,6 +237,26 @@ namespace Barotrauma
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
{
|
||||
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return 0.0f; }
|
||||
|
||||
if (currentEffect.AfflictionStatValues.TryGetValue(statType, out var value))
|
||||
{
|
||||
return MathHelper.Lerp(
|
||||
value.minValue,
|
||||
value.maxValue,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private AfflictionPrefab.Effect GetViableEffect()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) { return null; }
|
||||
return GetActiveEffect();
|
||||
}
|
||||
|
||||
public virtual void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in Prefab.PeriodicEffects)
|
||||
@@ -264,7 +284,11 @@ namespace Barotrauma
|
||||
|
||||
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
|
||||
{
|
||||
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
|
||||
float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier)
|
||||
: characterHealth.Character.GetStatValue(StatTypes.DebuffDurationMultiplier)));
|
||||
|
||||
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier * durationMultiplier;
|
||||
|
||||
}
|
||||
else // Reduce strengthening of afflictions if resistant
|
||||
{
|
||||
|
||||
+30
-7
@@ -212,6 +212,25 @@ namespace Barotrauma
|
||||
husk.Info.TeamID = CharacterTeamType.None;
|
||||
}
|
||||
|
||||
if (Prefab is AfflictionPrefabHusk huskPrefab)
|
||||
{
|
||||
if (huskPrefab.ControlHusk)
|
||||
{
|
||||
#if SERVER
|
||||
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.CharacterInfo.Character == character);
|
||||
if (client != null)
|
||||
{
|
||||
GameMain.Server.SetClientCharacter(client, husk);
|
||||
}
|
||||
#else
|
||||
if (!character.IsRemotelyControlled && character == Character.Controlled)
|
||||
{
|
||||
Character.Controlled = husk;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
if (limb.type == LimbType.None)
|
||||
@@ -229,15 +248,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if ((Prefab as AfflictionPrefabHusk)?.TransferBuffs ?? false)
|
||||
{
|
||||
foreach (Affliction affliction in character.CharacterHealth.Afflictions)
|
||||
{
|
||||
if (affliction.Prefab.IsBuff)
|
||||
{
|
||||
husk.CharacterHealth.ApplyAffliction(null, affliction.Prefab.Instantiate(affliction.Strength));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.Inventory != null && husk.Inventory != null)
|
||||
{
|
||||
if (character.Inventory.Capacity != husk.Inventory.Capacity)
|
||||
{
|
||||
string errorMsg = "Failed to move items from the source character's inventory into a husk's inventory (inventory sizes don't match)";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AfflictionHusk.CreateAIHusk:InventoryMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
for (int i = 0; i < character.Inventory.Capacity && i < husk.Inventory.Capacity; i++)
|
||||
{
|
||||
character.Inventory.GetItemsAt(i).ForEachMod(item => husk.Inventory.TryPutItem(item, i, true, false, null));
|
||||
|
||||
+90
-66
@@ -5,6 +5,7 @@ using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -91,9 +92,11 @@ namespace Barotrauma
|
||||
AttachLimbType = LimbType.None;
|
||||
}
|
||||
|
||||
TransferBuffs = element.GetAttributeBool("transferbuffs", true);
|
||||
SendMessages = element.GetAttributeBool("sendmessages", true);
|
||||
CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true);
|
||||
NeedsAir = element.GetAttributeBool("needsair", false);
|
||||
ControlHusk = element.GetAttributeBool("controlhusk", false);
|
||||
}
|
||||
|
||||
// Use any of these to define which limb the appendage is attached to.
|
||||
@@ -106,9 +109,11 @@ namespace Barotrauma
|
||||
public readonly string[] TargetSpecies;
|
||||
public const string Tag = "[speciesname]";
|
||||
|
||||
public readonly bool TransferBuffs;
|
||||
public readonly bool SendMessages;
|
||||
public readonly bool CauseSpeechImpediment;
|
||||
public readonly bool NeedsAir;
|
||||
public readonly bool ControlHusk;
|
||||
}
|
||||
|
||||
class AfflictionPrefab : IPrefab, IDisposable, IHasUintIdentifier
|
||||
@@ -116,83 +121,93 @@ namespace Barotrauma
|
||||
public class Effect
|
||||
{
|
||||
//this effect is applied when the strength is within this range
|
||||
public float MinStrength, MaxStrength;
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinVitalityDecrease { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxVitalityDecrease { get; private set; }
|
||||
|
||||
public readonly float MinVitalityDecrease = 0.0f;
|
||||
public readonly float MaxVitalityDecrease = 0.0f;
|
||||
|
||||
//how much the strength of the affliction changes per second
|
||||
public readonly float StrengthChange = 0.0f;
|
||||
[Serialize(0.0f, false)]
|
||||
public float StrengthChange { get; private set; }
|
||||
|
||||
public readonly bool MultiplyByMaxVitality;
|
||||
[Serialize(false, false)]
|
||||
public bool MultiplyByMaxVitality { get; private set; }
|
||||
|
||||
public float MinScreenBlurStrength, MaxScreenBlurStrength;
|
||||
public float MinScreenDistortStrength, MaxScreenDistortStrength;
|
||||
public float MinGrainStrength, MaxGrainStrength;
|
||||
public float MinRadialDistortStrength, MaxRadialDistortStrength;
|
||||
public float MinChromaticAberrationStrength, MaxChromaticAberrationStrength;
|
||||
public float MinSpeedMultiplier, MaxSpeedMultiplier;
|
||||
public float MinBuffMultiplier, MaxBuffMultiplier;
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinScreenBlurStrength { get; private set; }
|
||||
|
||||
public float MinSkillMultiplier, MaxSkillMultiplier;
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxScreenBlurStrength { get; private set; }
|
||||
|
||||
public float MinResistance, MaxResistance;
|
||||
public string ResistanceFor;
|
||||
public string DialogFlag;
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinScreenDistortStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxScreenDistortStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinRadialDistortStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxRadialDistortStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinChromaticAberrationStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxChromaticAberrationStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinGrainStrength { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxGrainStrength { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float MinBuffMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float MaxBuffMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float MinSpeedMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float MaxSpeedMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float MinSkillMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false)]
|
||||
public float MaxSkillMultiplier { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string ResistanceFor { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinResistance { get; private set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MaxResistance { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string DialogFlag { get; private set; }
|
||||
|
||||
public readonly Dictionary<StatTypes, (float minValue, float maxValue)> AfflictionStatValues = new Dictionary<StatTypes, (float minValue, float maxValue)>();
|
||||
|
||||
//statuseffects applied on the character when the affliction is active
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
|
||||
public Effect(XElement element, string parentDebugName)
|
||||
{
|
||||
MinStrength = element.GetAttributeFloat("minstrength", 0);
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 0);
|
||||
|
||||
MultiplyByMaxVitality = element.GetAttributeBool("multiplybymaxvitality", false);
|
||||
|
||||
MinVitalityDecrease = element.GetAttributeFloat("minvitalitydecrease", 0.0f);
|
||||
MaxVitalityDecrease = element.GetAttributeFloat("maxvitalitydecrease", 0.0f);
|
||||
MaxVitalityDecrease = Math.Max(MinVitalityDecrease, MaxVitalityDecrease);
|
||||
|
||||
MinScreenDistortStrength = element.GetAttributeFloat("minscreendistort", 0.0f);
|
||||
MaxScreenDistortStrength = element.GetAttributeFloat("maxscreendistort", 0.0f);
|
||||
MaxScreenDistortStrength = Math.Max(MinScreenDistortStrength, MaxScreenDistortStrength);
|
||||
|
||||
MinRadialDistortStrength = element.GetAttributeFloat("minradialdistort", 0.0f);
|
||||
MaxRadialDistortStrength = element.GetAttributeFloat("maxradialdistort", 0.0f);
|
||||
MaxRadialDistortStrength = Math.Max(MinRadialDistortStrength, MaxRadialDistortStrength);
|
||||
|
||||
MinChromaticAberrationStrength = element.GetAttributeFloat("minchromaticaberration", 0.0f);
|
||||
MaxChromaticAberrationStrength = element.GetAttributeFloat("maxchromaticaberration", 0.0f);
|
||||
MaxChromaticAberrationStrength = Math.Max(MinChromaticAberrationStrength, MaxChromaticAberrationStrength);
|
||||
|
||||
MinGrainStrength = element.GetAttributeFloat(nameof(MinGrainStrength).ToLower(), 0.0f);
|
||||
MaxGrainStrength = element.GetAttributeFloat(nameof(MaxGrainStrength).ToLower(), 0.0f);
|
||||
MaxGrainStrength = Math.Max(MinGrainStrength, MaxGrainStrength);
|
||||
|
||||
MinScreenBlurStrength = element.GetAttributeFloat("minscreenblur", 0.0f);
|
||||
MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f);
|
||||
MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength);
|
||||
|
||||
MinSkillMultiplier = element.GetAttributeFloat("minskillmultiplier", 1.0f);
|
||||
MaxSkillMultiplier = element.GetAttributeFloat("maxskillmultiplier", 1.0f);
|
||||
|
||||
ResistanceFor = element.GetAttributeString("resistancefor", "");
|
||||
MinResistance = element.GetAttributeFloat("minresistance", 0.0f);
|
||||
MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f);
|
||||
MaxResistance = Math.Max(MinResistance, MaxResistance);
|
||||
|
||||
MinSpeedMultiplier = element.GetAttributeFloat("minspeedmultiplier", 1.0f);
|
||||
MaxSpeedMultiplier = element.GetAttributeFloat("maxspeedmultiplier", 1.0f);
|
||||
MaxSpeedMultiplier = Math.Max(MinSpeedMultiplier, MaxSpeedMultiplier);
|
||||
|
||||
MinBuffMultiplier = element.GetAttributeFloat("minbuffmultiplier", 1.0f);
|
||||
MaxBuffMultiplier = element.GetAttributeFloat("maxbuffmultiplier", 1.0f);
|
||||
MaxBuffMultiplier = Math.Max(MinBuffMultiplier, MaxBuffMultiplier);
|
||||
|
||||
DialogFlag = element.GetAttributeString("dialogflag", "");
|
||||
|
||||
StrengthChange = element.GetAttributeFloat("strengthchange", 0.0f);
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -201,6 +216,15 @@ namespace Barotrauma
|
||||
case "statuseffect":
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
|
||||
break;
|
||||
case "statvalue":
|
||||
var statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), parentDebugName);
|
||||
|
||||
float defaultValue = subElement.GetAttributeFloat("value", 0f);
|
||||
float minValue = subElement.GetAttributeFloat("minvalue", defaultValue);
|
||||
float maxValue = subElement.GetAttributeFloat("maxvalue", defaultValue);
|
||||
|
||||
AfflictionStatValues.TryAdd(statType, (minValue, maxValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,7 +614,7 @@ namespace Barotrauma
|
||||
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLower(), 0.0f);
|
||||
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLowerInvariant(), 0.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
|
||||
|
||||
@@ -116,15 +116,15 @@ namespace Barotrauma
|
||||
private set => Character.Params.Health.CrushDepth = value;
|
||||
}
|
||||
|
||||
private List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||
private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||
//non-limb-specific afflictions
|
||||
private List<Affliction> afflictions = new List<Affliction>();
|
||||
private readonly List<Affliction> afflictions = new List<Affliction>();
|
||||
/// <summary>
|
||||
/// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions.
|
||||
/// </summary>
|
||||
public IEnumerable<Affliction> Afflictions => afflictions;
|
||||
|
||||
private HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
|
||||
private readonly HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
|
||||
private Affliction bloodlossAffliction;
|
||||
private Affliction oxygenLowAffliction;
|
||||
private Affliction pressureAffliction;
|
||||
@@ -151,6 +151,7 @@ namespace Barotrauma
|
||||
max += Character.Info.Job.Prefab.VitalityModifier;
|
||||
}
|
||||
max *= Character.StaticHealthMultiplier;
|
||||
max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier);
|
||||
return max * Character.HealthMultiplier;
|
||||
}
|
||||
}
|
||||
@@ -434,10 +435,21 @@ namespace Barotrauma
|
||||
float temp = afflictions[i].GetResistance(resistanceId);
|
||||
if (temp > resistance) resistance = temp;
|
||||
}
|
||||
resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(resistanceId));
|
||||
|
||||
return resistance;
|
||||
}
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
{
|
||||
float value = 0f;
|
||||
for (int i = 0; i < afflictions.Count; i++)
|
||||
{
|
||||
value += afflictions[i].GetStatValue(statType);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
|
||||
public void ReduceAffliction(Limb targetLimb, string affliction, float amount)
|
||||
{
|
||||
@@ -468,6 +480,11 @@ namespace Barotrauma
|
||||
for (int i = matchingAfflictions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var matchingAffliction = matchingAfflictions[i];
|
||||
|
||||
// kind of bad to create a tuple every time, but I can't think of another way to easily do this
|
||||
var afflictionReduction = (matchingAffliction, reduceAmount);
|
||||
Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction);
|
||||
|
||||
if (matchingAffliction.Strength < reduceAmount)
|
||||
{
|
||||
float surplus = reduceAmount - matchingAffliction.Strength;
|
||||
@@ -539,9 +556,9 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Instead of using the limbhealth count here, I think it's best to define the max vitality per limb roughly with a constant value.
|
||||
// Therefore with e.g. 80 health, the max damage per limb would be 20.
|
||||
// Having at least 20 damage on both legs would cause maximum limping.
|
||||
float max = MaxVitality / 4;
|
||||
// Therefore with e.g. 80 health, the max damage per limb would be 40.
|
||||
// Having at least 40 damage on both legs would cause maximum limping.
|
||||
float max = MaxVitality / 2;
|
||||
if (string.IsNullOrEmpty(afflictionType))
|
||||
{
|
||||
float damage = GetAfflictionStrength("damage", limb, true);
|
||||
@@ -738,7 +755,15 @@ namespace Barotrauma
|
||||
affliction.DamagePerSecondTimer += deltaTime;
|
||||
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
|
||||
}
|
||||
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
UpdateLimbAfflictionOverlays();
|
||||
|
||||
CalculateVitality();
|
||||
@@ -825,8 +850,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
|
||||
var causeOfDeath = GetCauseOfDeath();
|
||||
Character.Kill(causeOfDeath.First, causeOfDeath.Second);
|
||||
var (type, affliction) = GetCauseOfDeath();
|
||||
Character.Kill(type, affliction);
|
||||
#if CLIENT
|
||||
DisplayVitalityDelay = 0.0f;
|
||||
DisplayedVitality = Vitality;
|
||||
@@ -859,7 +884,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<CauseOfDeathType, Affliction> GetCauseOfDeath()
|
||||
public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath()
|
||||
{
|
||||
List<Affliction> currentAfflictions = GetAllAfflictions(true);
|
||||
|
||||
@@ -880,7 +905,7 @@ namespace Barotrauma
|
||||
causeOfDeath = Character.AnimController.InWater ? CauseOfDeathType.Drowning : CauseOfDeathType.Suffocation;
|
||||
}
|
||||
|
||||
return new Pair<CauseOfDeathType, Affliction>(causeOfDeath, strongestAffliction);
|
||||
return (causeOfDeath, strongestAffliction);
|
||||
}
|
||||
|
||||
// TODO: this method is called a lot (every half second) -> optimize, don't create new class instances and lists every time!
|
||||
@@ -968,7 +993,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
|
||||
private readonly List<Pair<LimbHealth, Affliction>> limbAfflictions = new List<Pair<LimbHealth, Affliction>>();
|
||||
private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>();
|
||||
public void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
activeAfflictions.Clear();
|
||||
@@ -999,22 +1024,22 @@ namespace Barotrauma
|
||||
foreach (Affliction limbAffliction in limbHealth.Afflictions)
|
||||
{
|
||||
if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) continue;
|
||||
limbAfflictions.Add(new Pair<LimbHealth, Affliction>(limbHealth, limbAffliction));
|
||||
limbAfflictions.Add((limbHealth, limbAffliction));
|
||||
}
|
||||
}
|
||||
|
||||
msg.Write((byte)limbAfflictions.Count);
|
||||
foreach (var limbAffliction in limbAfflictions)
|
||||
foreach (var (limbHealth, affliction) in limbAfflictions)
|
||||
{
|
||||
msg.WriteRangedInteger(limbHealths.IndexOf(limbAffliction.First), 0, limbHealths.Count - 1);
|
||||
msg.Write(limbAffliction.Second.Prefab.UIntIdentifier);
|
||||
msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1);
|
||||
msg.Write(affliction.Prefab.UIntIdentifier);
|
||||
msg.WriteRangedSingle(
|
||||
MathHelper.Clamp(limbAffliction.Second.Strength, 0.0f, limbAffliction.Second.Prefab.MaxStrength),
|
||||
0.0f, limbAffliction.Second.Prefab.MaxStrength, 8);
|
||||
msg.Write((byte)limbAffliction.Second.Prefab.PeriodicEffects.Count());
|
||||
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in limbAffliction.Second.Prefab.PeriodicEffects)
|
||||
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
|
||||
0.0f, affliction.Prefab.MaxStrength, 8);
|
||||
msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
|
||||
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects)
|
||||
{
|
||||
msg.WriteRangedSingle(limbAffliction.Second.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
|
||||
msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,10 +217,6 @@ namespace Barotrauma
|
||||
if (item.Prefab.Identifier == "idcard" || item.Prefab.Identifier == "idcardwreck")
|
||||
{
|
||||
item.AddTag("name:" + character.Name);
|
||||
if (Level.Loaded != null)
|
||||
{
|
||||
item.ReplaceTag("wreck_id", Level.Loaded.GetWreckIDTag("wreck_id", submarine));
|
||||
}
|
||||
var job = character.Info?.Job;
|
||||
if (job != null)
|
||||
{
|
||||
@@ -229,6 +225,10 @@ namespace Barotrauma
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
idCardComponent?.Initialize(character.Info);
|
||||
if (submarine != null && (submarine.Info.IsWreck || submarine.Info.IsOutpost))
|
||||
{
|
||||
idCardComponent.SubmarineSpecificID = submarine.SubmarineSpecificIDTag;
|
||||
}
|
||||
|
||||
var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in idCardTags)
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace Barotrauma
|
||||
|
||||
public bool inWater;
|
||||
|
||||
private readonly FixedMouseJoint pullJoint;
|
||||
private FixedMouseJoint pullJoint;
|
||||
|
||||
public readonly LimbType type;
|
||||
|
||||
@@ -683,7 +683,7 @@ namespace Barotrauma
|
||||
private readonly List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
||||
private readonly List<DamageModifier> tempModifiers = new List<DamageModifier>();
|
||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f)
|
||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
||||
{
|
||||
appliedDamageModifiers.Clear();
|
||||
afflictionsCopy.Clear();
|
||||
@@ -741,7 +741,7 @@ namespace Barotrauma
|
||||
{
|
||||
newAffliction.SetStrength(affliction.NonClampedStrength);
|
||||
}
|
||||
|
||||
attacker?.CheckTalents(AbilityEffectType.OnAddDamageAffliction, newAffliction);
|
||||
if (applyAffliction)
|
||||
{
|
||||
afflictionsCopy.Add(newAffliction);
|
||||
@@ -1263,6 +1263,14 @@ namespace Barotrauma
|
||||
{
|
||||
body?.Remove();
|
||||
body = null;
|
||||
if (pullJoint != null)
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(pullJoint))
|
||||
{
|
||||
GameMain.World.Remove(pullJoint);
|
||||
}
|
||||
pullJoint = null;
|
||||
}
|
||||
Release();
|
||||
RemoveProjSpecific();
|
||||
Removed = true;
|
||||
|
||||
@@ -70,6 +70,9 @@ namespace Barotrauma
|
||||
[Serialize(1f, true), Editable]
|
||||
public float BleedParticleMultiplier { get; private set; }
|
||||
|
||||
[Serialize(true, true, description: "Can the creature eat bodies? Used by player controlled creatures to allow them to eat. Currently applicable only to non-humanoids. To allow an AI controller to eat, just add an ai target with the state \"eat\""), Editable]
|
||||
public bool CanEat { get; set; }
|
||||
|
||||
[Serialize(10f, true, description: "How effectively/easily the character eats other characters. Affects the forces, the amount of particles, and the time required before the target is eaten away"), Editable(MinValueFloat = 1, MaxValueFloat = 1000, ValueStep = 1)]
|
||||
public float EatingSpeed { get; set; }
|
||||
|
||||
@@ -88,6 +91,9 @@ namespace Barotrauma
|
||||
[Serialize(25000f, true, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)]
|
||||
public float DisableDistance { get; set; }
|
||||
|
||||
[Serialize(10f, true, "How frequent the recurring idle and attack sounds are?"), Editable(MinValueFloat = 1f, MaxValueFloat = 100f)]
|
||||
public float SoundInterval { get; set; }
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public XDocument VariantFile { get; private set; }
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityCondition
|
||||
{
|
||||
protected CharacterTalent characterTalent;
|
||||
protected Character character;
|
||||
protected bool invert;
|
||||
|
||||
public virtual bool AllowClientSimulation => true;
|
||||
|
||||
public AbilityCondition(CharacterTalent characterTalent, XElement conditionElement)
|
||||
{
|
||||
this.characterTalent = characterTalent;
|
||||
character = characterTalent.Character;
|
||||
invert = conditionElement.GetAttributeBool("invert", false);
|
||||
}
|
||||
public abstract bool MatchesCondition(object abilityData);
|
||||
public abstract bool MatchesCondition();
|
||||
|
||||
|
||||
// tools
|
||||
protected enum TargetType
|
||||
{
|
||||
Any = 0,
|
||||
Enemy = 1,
|
||||
Ally = 2,
|
||||
NotSelf = 3,
|
||||
Alive = 4,
|
||||
Monster = 5,
|
||||
};
|
||||
|
||||
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
|
||||
{
|
||||
List<TargetType> targetTypes = new List<TargetType>();
|
||||
foreach (string targetTypeString in targetTypeStrings)
|
||||
{
|
||||
TargetType targetType = TargetType.Any;
|
||||
if (!Enum.TryParse(targetTypeString, true, out targetType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
|
||||
}
|
||||
targetTypes.Add(targetType);
|
||||
}
|
||||
return targetTypes;
|
||||
}
|
||||
|
||||
protected bool IsViableTarget(IEnumerable<TargetType> targetTypes, Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter == null) { return false; }
|
||||
|
||||
bool isViable = true;
|
||||
foreach (TargetType targetType in targetTypes)
|
||||
{
|
||||
if (!IsViableTarget(targetType, targetCharacter))
|
||||
{
|
||||
isViable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isViable;
|
||||
}
|
||||
|
||||
private bool IsViableTarget(TargetType targetType, Character targetCharacter)
|
||||
{
|
||||
switch (targetType)
|
||||
{
|
||||
case TargetType.Enemy:
|
||||
return !HumanAIController.IsFriendly(character, targetCharacter);
|
||||
case TargetType.Ally:
|
||||
return HumanAIController.IsFriendly(character, targetCharacter);
|
||||
case TargetType.NotSelf:
|
||||
return targetCharacter != character;
|
||||
case TargetType.Alive:
|
||||
return !targetCharacter.IsDead;
|
||||
case TargetType.Monster:
|
||||
return !targetCharacter.IsHuman;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAttackData : AbilityConditionData
|
||||
{
|
||||
private enum WeaponType
|
||||
{
|
||||
Any = 0,
|
||||
Melee = 1,
|
||||
Ranged = 2
|
||||
};
|
||||
|
||||
private readonly string itemIdentifier;
|
||||
private readonly string[] tags;
|
||||
private WeaponType weapontype;
|
||||
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
|
||||
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
|
||||
switch (conditionElement.GetAttributeString("weapontype", ""))
|
||||
{
|
||||
case "melee":
|
||||
weapontype = WeaponType.Melee;
|
||||
break;
|
||||
case "ranged":
|
||||
weapontype = WeaponType.Ranged;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is AttackData attackData)
|
||||
{
|
||||
Item item = attackData?.SourceAttack?.SourceItem;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Source Item was not found in {this} for talent {characterTalent.DebugIdentifier}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(itemIdentifier))
|
||||
{
|
||||
if (item.prefab.Identifier != itemIdentifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tags.Any())
|
||||
{
|
||||
if (!tags.All(t => item.HasTag(t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (weapontype)
|
||||
{
|
||||
case WeaponType.Melee:
|
||||
return item.GetComponent<MeleeWeapon>() != null;
|
||||
case WeaponType.Ranged:
|
||||
return item.GetComponent<RangedWeapon>() != null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(AttackData));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAttackResult : AbilityConditionData
|
||||
{
|
||||
private readonly List<TargetType> targetTypes;
|
||||
private readonly string[] afflictions;
|
||||
public AbilityConditionAttackResult(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
|
||||
afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is AttackResult attackResult)
|
||||
{
|
||||
if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; }
|
||||
|
||||
if (afflictions.Any())
|
||||
{
|
||||
if (!afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(AttackData));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionCharacter : AbilityConditionData
|
||||
{
|
||||
private readonly List<TargetType> targetTypes;
|
||||
|
||||
public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is Character character)
|
||||
{
|
||||
if (!IsViableTarget(targetTypes, character)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Character));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityConditionData : AbilityCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Some conditions rely on specific ability data that is integrally connected to the AbilityEffectType.
|
||||
/// This is done in order to avoid having to create duplicate ability behavior, such as if an ability needs to trigger
|
||||
/// a common ability effect but in specific circumstances. These conditions could also be partially replaced by
|
||||
/// more explicit AbilityEffectType enums, but this would introduce bloat and overhead to integral game logic
|
||||
/// when instead said logic can be made to only run when required using these conditions.
|
||||
///
|
||||
/// These conditions will return an error if used outside their limited intended use.
|
||||
/// </summary>
|
||||
public AbilityConditionData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected void LogAbilityConditionError<T>(T abilityData, Type expectedData)
|
||||
{
|
||||
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityData}");
|
||||
}
|
||||
|
||||
protected abstract bool MatchesConditionSpecific(object abilityData);
|
||||
public override bool MatchesCondition()
|
||||
{
|
||||
DebugConsole.ThrowError("Used data-reliant ability condition in a state-based ability! This is not allowed.");
|
||||
return false;
|
||||
}
|
||||
public override bool MatchesCondition(object abilityData)
|
||||
{
|
||||
if (abilityData is null) { return invert; }
|
||||
return invert ? !MatchesConditionSpecific(abilityData) : MatchesConditionSpecific(abilityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionEvasiveManeuvers : AbilityConditionData
|
||||
{
|
||||
public AbilityConditionEvasiveManeuvers(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is Submarine submarine)
|
||||
{
|
||||
return submarine.TeamID == character.TeamID && character.Submarine == submarine;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Submarine));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHandsomeStranger : AbilityConditionData
|
||||
{
|
||||
string skillIdentifier;
|
||||
|
||||
public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is string skillIdentifier)
|
||||
{
|
||||
return this.skillIdentifier == skillIdentifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(string));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItem : AbilityConditionData
|
||||
{
|
||||
private readonly string identifier;
|
||||
private readonly string[] tags;
|
||||
|
||||
public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant();
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
ItemPrefab item = null;
|
||||
if (abilityData is Item tempItem)
|
||||
{
|
||||
item = tempItem.Prefab;
|
||||
}
|
||||
// this and other instances of this type of casting will be refactored
|
||||
else if (abilityData is (ItemPrefab itemPrefab, object _))
|
||||
{
|
||||
item = itemPrefab;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
if (item.Identifier != identifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return tags.Any(t => item.Tags.Any(p => t == p));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Item));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionReduceAffliction : AbilityConditionData
|
||||
{
|
||||
private readonly string[] allowedTypes;
|
||||
private readonly string identifier;
|
||||
|
||||
public AbilityConditionReduceAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
allowedTypes = conditionElement.GetAttributeStringArray("allowedtypes", new string[0], convertToLowerInvariant: true);
|
||||
identifier = conditionElement.GetAttributeString("identifier", "");
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is (Affliction affliction, float reduceAmount))
|
||||
{
|
||||
if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; }
|
||||
|
||||
if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof((Affliction, float)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionScavenger : AbilityConditionData
|
||||
{
|
||||
public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is Item item)
|
||||
{
|
||||
return item.Submarine != character.Submarine;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof(Item));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAboveVitality : AbilityConditionDataless
|
||||
{
|
||||
float vitalityPercentage;
|
||||
|
||||
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.HealthPercentage / 100f > vitalityPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAlliesAboveVitality : AbilityConditionDataless
|
||||
{
|
||||
float vitalityPercentage;
|
||||
|
||||
public AbilityConditionAlliesAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f);
|
||||
}
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return Character.GetFriendlyCrew(character).All(c => c.HealthPercentage / 100f >= vitalityPercentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionCrouched : AbilityConditionDataless
|
||||
{
|
||||
|
||||
public AbilityConditionCrouched(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.AnimController is HumanoidAnimController humanoidAnimController && humanoidAnimController.Crouching;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityConditionDataless : AbilityCondition
|
||||
{
|
||||
public AbilityConditionDataless(CharacterTalent characterTalent, XElement conditionElement) : base (characterTalent, conditionElement) { }
|
||||
|
||||
protected abstract bool MatchesConditionSpecific();
|
||||
public override bool MatchesCondition()
|
||||
{
|
||||
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
|
||||
}
|
||||
|
||||
public override bool MatchesCondition(object abilityData)
|
||||
{
|
||||
return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasAffliction : AbilityConditionDataless
|
||||
{
|
||||
private string afflictionIdentifier;
|
||||
private float minimumPercentage;
|
||||
|
||||
|
||||
public AbilityConditionHasAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
afflictionIdentifier = conditionElement.GetAttributeString("afflictionidentifier", "");
|
||||
minimumPercentage = conditionElement.GetAttributeFloat("minimumpercentage", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(afflictionIdentifier))
|
||||
{
|
||||
var affliction = character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
|
||||
if (affliction == null) { return false; }
|
||||
|
||||
return minimumPercentage <= affliction.Strength / affliction.Prefab.MaxStrength;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasDifferentJobs : AbilityConditionDataless
|
||||
{
|
||||
private readonly int amount;
|
||||
public AbilityConditionHasDifferentJobs(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
amount = conditionElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
IEnumerable<Character> crewmembers = Character.GetFriendlyCrew(character);
|
||||
int differentCrewAmount = crewmembers.Select(c => c.Info?.Job?.Prefab.Identifier).Distinct().Count();
|
||||
return differentCrewAmount >= amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasItem : AbilityConditionDataless
|
||||
{
|
||||
// not used for anything atm, will be used for clown subclass
|
||||
private readonly string[] tags;
|
||||
private InvSlotType? invSlotType;
|
||||
bool requireAll;
|
||||
|
||||
private List<Item> items = new List<Item>();
|
||||
|
||||
public AbilityConditionHasItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
|
||||
requireAll = conditionElement.GetAttributeBool("requireall", false);
|
||||
//this.invSlotType = invSlotType;
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
items.Clear();
|
||||
if (tags.Any())
|
||||
{
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
// there is a better method, should use that instead
|
||||
if (character.GetEquippedItem(tag, invSlotType) is Item foundItem)
|
||||
{
|
||||
items.Add(foundItem);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.GetEquippedItem(null, invSlotType) is Item foundItem)
|
||||
{
|
||||
items.Add(foundItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (requireAll)
|
||||
{
|
||||
return (items.Count >= tags.Count());
|
||||
}
|
||||
else
|
||||
{
|
||||
return items.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionInWater : AbilityConditionDataless
|
||||
{
|
||||
public AbilityConditionInWater(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.InWater;
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionMission : AbilityConditionData
|
||||
{
|
||||
private readonly MissionType missionType;
|
||||
public AbilityConditionMission(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
string missionTypeString = conditionElement.GetAttributeString("missiontype", "None");
|
||||
if (!Enum.TryParse(missionTypeString, out missionType))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in AbilityConditionMission \"" + characterTalent.DebugIdentifier + "\" - \"" + missionTypeString + "\" is not a valid mission type.");
|
||||
return;
|
||||
}
|
||||
if (missionType == MissionType.None)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in AbilityConditionMission \"" + characterTalent.DebugIdentifier + "\" - mission type cannot be none.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(object abilityData)
|
||||
{
|
||||
if (abilityData is (Mission mission, AbilityValue missionAbilityValue))
|
||||
{
|
||||
return mission.Prefab.Type == missionType;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionNoCrewDied : AbilityConditionDataless
|
||||
{
|
||||
public AbilityConditionNoCrewDied(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
|
||||
{
|
||||
return !campaign.CrewHasDied;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionOnMission : AbilityConditionDataless
|
||||
{
|
||||
public AbilityConditionOnMission(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return Level.Loaded?.Type != LevelData.LevelType.Outpost;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionRagdolled : AbilityConditionDataless
|
||||
{
|
||||
|
||||
public AbilityConditionRagdolled(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.IsRagdolled || character.Stun > 0f || character.IsIncapacitated;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionRunning : AbilityConditionDataless
|
||||
{
|
||||
public AbilityConditionRunning(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return character.AnimController is HumanoidAnimController animController && animController.IsMovingFast;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionServerRandom : AbilityConditionDataless
|
||||
{
|
||||
private float randomChance = 0f;
|
||||
public override bool AllowClientSimulation => false;
|
||||
|
||||
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
randomChance = conditionElement.GetAttributeFloat("randomchance", 1f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
return randomChance >= Rand.Range(0f, 1f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionShipFlooded : AbilityConditionDataless
|
||||
{
|
||||
private readonly float floodPercentage;
|
||||
public AbilityConditionShipFlooded(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
floodPercentage = conditionElement.GetAttributeFloat("floodpercentage", 0f);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (character.Submarine == null || character.Submarine.TeamID != character.TeamID) { return false; }
|
||||
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
|
||||
return currentFloodPercentage / 100 > floodPercentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class CharacterAbility
|
||||
{
|
||||
public CharacterAbilityGroup CharacterAbilityGroup { get; }
|
||||
public CharacterTalent CharacterTalent { get; }
|
||||
public Character Character { get; }
|
||||
|
||||
public virtual bool RequiresAlive => true;
|
||||
public virtual bool AllowClientSimulation => false;
|
||||
public virtual bool AppliesEffectOnIntervalUpdate => false;
|
||||
|
||||
private const float DefaultEffectTime = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Used primarily for StatusEffects. Default to constant outside interval abilities.
|
||||
/// </summary>
|
||||
protected float EffectDeltaTime => CharacterAbilityGroup is CharacterAbilityGroupInterval abilityGroupInterval ? abilityGroupInterval.TimeSinceLastUpdate : DefaultEffectTime;
|
||||
|
||||
public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement)
|
||||
{
|
||||
CharacterAbilityGroup = characterAbilityGroup;
|
||||
CharacterTalent = characterAbilityGroup.CharacterTalent;
|
||||
Character = CharacterTalent.Character;
|
||||
}
|
||||
|
||||
public bool IsViable()
|
||||
{
|
||||
if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
if (RequiresAlive && Character.IsDead) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void InitializeAbility(bool addingFirstTime) { }
|
||||
|
||||
public virtual void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
// may need a separate Update for changing state on non-interval-based abilities
|
||||
if (AppliesEffectOnIntervalUpdate)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyState(conditionsMatched, timeSinceLastUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.");
|
||||
}
|
||||
|
||||
public void ApplyAbilityEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is null)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffect(abilityData);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect()
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect");
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect(object abilityData)
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect");
|
||||
}
|
||||
|
||||
protected void LogAbilityDataMismatch()
|
||||
{
|
||||
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type.");
|
||||
}
|
||||
|
||||
// XML
|
||||
public static CharacterAbility Load(XElement abilityElement, CharacterAbilityGroup characterAbilityGroup, bool errorMessages = true)
|
||||
{
|
||||
Type abilityType;
|
||||
string type = abilityElement.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
|
||||
if (abilityType == null)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
object[] args = { characterAbilityGroup, abilityElement };
|
||||
CharacterAbility characterAbility;
|
||||
|
||||
try
|
||||
{
|
||||
characterAbility = (CharacterAbility)Activator.CreateInstance(abilityType, args);
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException);
|
||||
return null;
|
||||
}
|
||||
|
||||
DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier);
|
||||
return characterAbility;
|
||||
}
|
||||
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
|
||||
{
|
||||
AbilityFlags flagType = AbilityFlags.None;
|
||||
if (!Enum.TryParse(flagTypeString, true, out flagType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
|
||||
}
|
||||
return flagType;
|
||||
}
|
||||
|
||||
public static float DistanceToSquaredDistance(float distance)
|
||||
{
|
||||
return distance * distance;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyForce : CharacterAbility
|
||||
{
|
||||
private readonly float impulseStrength;
|
||||
private readonly float maxVelocity;
|
||||
|
||||
private readonly string afflictionIdentifier;
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
impulseStrength = abilityElement.GetAttributeFloat("impulsestrength", 0f);
|
||||
maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f);
|
||||
|
||||
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
|
||||
if (affliction == null) { return; }
|
||||
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * impulseStrength * (affliction.Strength / affliction.Prefab.MaxStrength), maxVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffects : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public override bool AllowClientSimulation => true;
|
||||
|
||||
protected readonly List<StatusEffect> statusEffects;
|
||||
|
||||
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
}
|
||||
|
||||
protected void ApplyEffectSpecific(Character targetCharacter)
|
||||
{
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character targetCharacter)
|
||||
{
|
||||
ApplyEffectSpecific(targetCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToNearestAlly : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
protected float squaredMaxDistance;
|
||||
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
|
||||
{
|
||||
closestCharacter = crewCharacter;
|
||||
closestDistance = tempDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToRandomAlly : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
private readonly float squaredMaxDistance;
|
||||
private readonly bool allowDifferentSub;
|
||||
private readonly bool allowSelf;
|
||||
|
||||
public override bool AllowClientSimulation => false;
|
||||
|
||||
public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
|
||||
allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true);
|
||||
allowSelf = abilityElement.GetAttributeBool("allowself", true);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character chosenCharacter = null;
|
||||
|
||||
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
|
||||
(allowSelf ||c != Character) &&
|
||||
(allowDifferentSub || c.Submarine == Character.Submarine) &&
|
||||
Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance &&
|
||||
tempDistance < squaredMaxDistance).GetRandom();
|
||||
|
||||
if (chosenCharacter == null) { return; }
|
||||
|
||||
ApplyEffectSpecific(chosenCharacter);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveFlag : CharacterAbility
|
||||
{
|
||||
private AbilityFlags abilityFlag;
|
||||
|
||||
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
|
||||
public CharacterAbilityGiveFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.AddAbilityFlag(abilityFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveMissionCount : CharacterAbility
|
||||
{
|
||||
private readonly int amount;
|
||||
|
||||
public CharacterAbilityGiveMissionCount(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (!addingFirstTime) { return; }
|
||||
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; }
|
||||
campaign.Settings.AddedMissionCount += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveMoney : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
private int amount;
|
||||
|
||||
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
Character.GiveMoney(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGivePermanentStat : CharacterAbility
|
||||
{
|
||||
private readonly string statIdentifier;
|
||||
private readonly StatTypes statType;
|
||||
private readonly float value;
|
||||
private readonly bool targetAllies;
|
||||
private readonly bool removeOnDeath;
|
||||
//private readonly float maximumValue;
|
||||
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
|
||||
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
|
||||
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific()
|
||||
{
|
||||
if (targetAllies)
|
||||
{
|
||||
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath));
|
||||
}
|
||||
else
|
||||
{
|
||||
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveResistance : CharacterAbility
|
||||
{
|
||||
private string resistanceId;
|
||||
private float resistance;
|
||||
|
||||
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
|
||||
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, resistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveStat : CharacterAbility
|
||||
{
|
||||
private StatTypes statType;
|
||||
private float value;
|
||||
|
||||
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
|
||||
public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.ChangeStat(statType, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityIncreaseSkill : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
|
||||
private string skillIdentifier;
|
||||
private float skillIncrease;
|
||||
|
||||
public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
|
||||
skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character character)
|
||||
{
|
||||
ApplyEffectSpecific(character);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(Character character)
|
||||
{
|
||||
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyAffliction : CharacterAbility
|
||||
{
|
||||
private readonly string[] afflictionIdentifiers;
|
||||
|
||||
private readonly float addedMultiplier;
|
||||
|
||||
public CharacterAbilityModifyAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
afflictionIdentifiers = abilityElement.GetAttributeStringArray("afflictionidentifiers", new string[0], convertToLowerInvariant: true);
|
||||
addedMultiplier = abilityElement.GetAttributeFloat("addedmultiplier", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Affliction affliction)
|
||||
{
|
||||
foreach (string afflictionIdentifier in afflictionIdentifiers)
|
||||
{
|
||||
if (affliction.Identifier == afflictionIdentifier)
|
||||
{
|
||||
affliction.Strength *= 1 + addedMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityDataMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyAttackData : CharacterAbility
|
||||
{
|
||||
private readonly List<Affliction> afflictions;
|
||||
|
||||
float addedDamageMultiplier;
|
||||
float addedPenetration;
|
||||
|
||||
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
if (abilityElement.GetChildElement("afflictions") is XElement afflictionElements)
|
||||
{
|
||||
afflictions = CharacterAbilityGroup.ParseAfflictions(CharacterTalent, afflictionElements);
|
||||
}
|
||||
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
|
||||
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is AttackData attackData)
|
||||
{
|
||||
if (attackData.Afflictions == null)
|
||||
{
|
||||
attackData.Afflictions = afflictions;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackData.Afflictions.AddRange(afflictions);
|
||||
}
|
||||
attackData.DamageMultiplier += addedDamageMultiplier;
|
||||
attackData.AddedPenetration += addedPenetration;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityDataMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyFlag : CharacterAbility
|
||||
{
|
||||
private AbilityFlags abilityFlag;
|
||||
|
||||
private bool lastState;
|
||||
|
||||
public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier);
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
{
|
||||
Character.AddAbilityFlag(abilityFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
Character.RemoveAbilityFlag(abilityFlag);
|
||||
}
|
||||
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyReduceAffliction : CharacterAbility
|
||||
{
|
||||
float addedAmountMultiplier;
|
||||
|
||||
public CharacterAbilityModifyReduceAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedAmountMultiplier = abilityElement.GetAttributeFloat("addedamountmultiplier", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is (Affliction affliction, float reduceAmount))
|
||||
{
|
||||
affliction.Strength -= addedAmountMultiplier * reduceAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAbilityDataMismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyResistance : CharacterAbility
|
||||
{
|
||||
private string resistanceId;
|
||||
private float resistance;
|
||||
bool lastState;
|
||||
|
||||
// should probably be split to different classes
|
||||
public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
|
||||
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
|
||||
}
|
||||
|
||||
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance);
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyStat : CharacterAbility
|
||||
{
|
||||
private readonly StatTypes statType;
|
||||
private readonly float value;
|
||||
bool lastState;
|
||||
|
||||
public CharacterAbilityModifyStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
Character.ChangeStat(statType, conditionsMatched ? value : -value);
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyValue : CharacterAbility
|
||||
{
|
||||
private float addedValue;
|
||||
private float multiplierValue;
|
||||
|
||||
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
|
||||
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is AbilityValue abilityValue)
|
||||
{
|
||||
ApplyEffectSpecific(abilityValue);
|
||||
}
|
||||
else if (abilityData is (object _, AbilityValue tupleAbilityValue))
|
||||
{
|
||||
ApplyEffectSpecific(tupleAbilityValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(AbilityValue abilityValue)
|
||||
{
|
||||
abilityValue.Value += addedValue;
|
||||
abilityValue.Value *= multiplierValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// this seems like a real silly way to have to pass values by reference into these same interfaces
|
||||
// if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead
|
||||
class AbilityValue
|
||||
{
|
||||
public float Value { get; set; }
|
||||
public AbilityValue(float value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPutItem : CharacterAbility
|
||||
{
|
||||
private readonly string itemIdentifier;
|
||||
private readonly int amount;
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public CharacterAbilityPutItem(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
itemIdentifier = abilityElement.GetAttributeString("itemidentifier", "");
|
||||
amount = abilityElement.GetAttributeInt("amount", 1);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined.");
|
||||
return;
|
||||
}
|
||||
|
||||
ItemPrefab itemPrefab = ItemPrefab.Find(null, itemIdentifier);
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found.");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (GameMain.GameSession?.RoundEnding ?? true)
|
||||
{
|
||||
Item item = new Item(itemPrefab, Character.WorldPosition, Character.Submarine);
|
||||
Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any });
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, Character.Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityResetPermanentStat : CharacterAbility
|
||||
{
|
||||
private readonly string statIdentifier;
|
||||
public override bool RequiresAlive => false;
|
||||
|
||||
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
|
||||
}
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
ApplyEffectSpecific();
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific()
|
||||
{
|
||||
Character?.Info.ResetSavedStatValue(statIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApprenticeship : CharacterAbility
|
||||
{
|
||||
public CharacterAbilityApprenticeship(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is (string skillIdentifier, Character character) && character != Character)
|
||||
{
|
||||
character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityBountyHunter : CharacterAbility
|
||||
{
|
||||
private float vitalityPercentage;
|
||||
|
||||
public CharacterAbilityBountyHunter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
vitalityPercentage = abilityElement.GetAttributeFloat("vitalitypercentage", 0f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character character)
|
||||
{
|
||||
Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityIndustrialRevolution : CharacterAbility
|
||||
{
|
||||
float addedFabricationSpeed;
|
||||
|
||||
public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f);
|
||||
}
|
||||
|
||||
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
{
|
||||
// not necessarily the cleanest or performant way, but at least this shouldn't break anything.
|
||||
// must be done every frame in order to work.
|
||||
if (Character.SelectedConstruction?.GetComponent<Fabricator>() is Fabricator fabricator && fabricator.IsActive)
|
||||
{
|
||||
fabricator.FabricationSpeedMultiplier += addedFabricationSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityInsurancePolicy : CharacterAbility
|
||||
{
|
||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||
public override bool RequiresAlive => false;
|
||||
|
||||
private readonly int moneyPerLevel;
|
||||
private bool hasOccurred = false;
|
||||
|
||||
private static List<Client> clientsAlreadyUsed = new List<Client>();
|
||||
|
||||
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character?.Info is CharacterInfo info && !hasOccurred)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
|
||||
{
|
||||
if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel());
|
||||
hasOccurred = true;
|
||||
|
||||
// this is an ugly way to do this, but this effect should not occur more than once per round for a client
|
||||
// this seemed like the simplest way to do it since characters are instantiated from scratch each time
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
|
||||
{
|
||||
if (client.Character == Character)
|
||||
{
|
||||
clientsAlreadyUsed.Add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityMultitasker : CharacterAbility
|
||||
{
|
||||
private string lastSkillIdentifier;
|
||||
|
||||
public CharacterAbilityMultitasker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is string skillIdentifier)
|
||||
{
|
||||
if (skillIdentifier != lastSkillIdentifier)
|
||||
{
|
||||
lastSkillIdentifier = skillIdentifier;
|
||||
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, Character.Position + Vector2.UnitY * 175.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPsychoClown : CharacterAbility
|
||||
{
|
||||
private StatTypes statType;
|
||||
private float value;
|
||||
private string afflictionIdentifier;
|
||||
private float lastValue = 0f;
|
||||
|
||||
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
// managing state this way seems liable to cause bugs, maybe instead create abstraction to reset these values more safely
|
||||
// talents cannot be removed while in active play because of the lack of this, for example
|
||||
Character.ChangeStat(statType, -lastValue);
|
||||
|
||||
if (conditionsMatched)
|
||||
{
|
||||
var affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier);
|
||||
|
||||
float afflictionStrength = 0f;
|
||||
if (affliction != null)
|
||||
{
|
||||
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
|
||||
}
|
||||
|
||||
lastValue = afflictionStrength * value;
|
||||
Character.ChangeStat(statType, lastValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastValue = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityRegenerateLoot : CharacterAbility
|
||||
{
|
||||
List<Item> openedContainers = new List<Item>();
|
||||
|
||||
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Item item && !openedContainers.Contains(item))
|
||||
{
|
||||
openedContainers.Add(item);
|
||||
|
||||
if (item.GetComponent<ItemContainer>() is ItemContainer itemContainer)
|
||||
{
|
||||
AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityStonewall : CharacterAbility
|
||||
{
|
||||
private readonly List<StatusEffect> statusEffects;
|
||||
private readonly List<StatusEffect> statusEffectsReset;
|
||||
private int maxEnemyCount;
|
||||
private float squaredDistance;
|
||||
|
||||
public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset"));
|
||||
maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0);
|
||||
squaredDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("distance", 0));
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
int numberOfEnemiesInRange = Character.CharacterList.Where(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) < squaredDistance).Count();
|
||||
|
||||
foreach (var statusEffect in statusEffectsReset)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character);
|
||||
}
|
||||
|
||||
if (conditionsMatched && numberOfEnemiesInRange > 0)
|
||||
{
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
|
||||
{
|
||||
private string tag;
|
||||
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
tag = abilityElement.GetAttributeString("tag", "");
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
|
||||
|
||||
Character closestCharacter = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
|
||||
{
|
||||
closestCharacter = crewCharacter;
|
||||
closestDistance = tempDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
|
||||
|
||||
if (closestDistance < squaredMaxDistance)
|
||||
{
|
||||
ApplyEffectSpecific(Character);
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityTaskmaster : CharacterAbility
|
||||
{
|
||||
private readonly List<StatusEffect> statusEffects;
|
||||
private readonly List<StatusEffect> statusEffectsRemove;
|
||||
|
||||
private Character lastCharacter;
|
||||
|
||||
public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove"));
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(object abilityData)
|
||||
{
|
||||
if (abilityData is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter == Character) { return; }
|
||||
|
||||
foreach (var statusEffect in statusEffectsRemove)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter);
|
||||
}
|
||||
|
||||
foreach (var statusEffect in statusEffects)
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
|
||||
}
|
||||
|
||||
lastCharacter = targetCharacter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class CharacterAbilityGroup
|
||||
{
|
||||
public CharacterTalent CharacterTalent { get; }
|
||||
public Character Character { get; }
|
||||
|
||||
// currently only used to turn off simulation if random conditions are in use
|
||||
public bool IsActive { get; private set; } = true;
|
||||
|
||||
// add support for OR conditions?
|
||||
protected readonly List<AbilityCondition> abilityConditions = new List<AbilityCondition>();
|
||||
|
||||
// separate dictionaries for each type of characterability?
|
||||
protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>();
|
||||
|
||||
public CharacterAbilityGroup(CharacterTalent characterTalent, XElement abilityElementGroup)
|
||||
{
|
||||
CharacterTalent = characterTalent;
|
||||
Character = CharacterTalent.Character;
|
||||
|
||||
foreach (XElement subElement in abilityElementGroup.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "abilities":
|
||||
LoadAbilities(subElement);
|
||||
break;
|
||||
case "conditions":
|
||||
LoadConditions(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateAbilityGroup(bool addingFirstTime)
|
||||
{
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
characterAbility.InitializeAbility(addingFirstTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadConditions(XElement conditionElements)
|
||||
{
|
||||
foreach (XElement conditionElement in conditionElements.Elements())
|
||||
{
|
||||
AbilityCondition newCondition = ConstructCondition(CharacterTalent, conditionElement);
|
||||
|
||||
if (newCondition == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newCondition.AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
abilityConditions.Add(newCondition);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAbility(CharacterAbility characterAbility)
|
||||
{
|
||||
if (characterAbility == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!");
|
||||
return;
|
||||
}
|
||||
|
||||
characterAbilities.Add(characterAbility);
|
||||
}
|
||||
|
||||
// XML
|
||||
private AbilityCondition ConstructCondition(CharacterTalent characterTalent, XElement conditionElement, bool errorMessages = true)
|
||||
{
|
||||
AbilityCondition newCondition = null;
|
||||
|
||||
Type conditionType;
|
||||
string type = conditionElement.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
conditionType = Type.GetType("Barotrauma.Abilities." + type + "", false, true);
|
||||
if (conditionType == null)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
object[] args = { characterTalent, conditionElement };
|
||||
|
||||
try
|
||||
{
|
||||
newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args);
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (newCondition == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null");
|
||||
return null;
|
||||
}
|
||||
|
||||
return newCondition;
|
||||
}
|
||||
|
||||
private void LoadAbilities(XElement abilityElements)
|
||||
{
|
||||
foreach (XElement abilityElementGroup in abilityElements.Elements())
|
||||
{
|
||||
AddAbility(ConstructAbility(abilityElementGroup, CharacterTalent));
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterAbility ConstructAbility(XElement abilityElement, CharacterTalent characterTalent)
|
||||
{
|
||||
CharacterAbility newAbility = CharacterAbility.Load(abilityElement, this);
|
||||
|
||||
if (newAbility == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return newAbility;
|
||||
}
|
||||
|
||||
public static List<StatusEffect> ParseStatusEffects(CharacterTalent characterTalent, XElement statusEffectElements)
|
||||
{
|
||||
if (statusEffectElements == null)
|
||||
{
|
||||
DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
foreach (XElement statusEffectElement in statusEffectElements.Elements())
|
||||
{
|
||||
var statusEffect = StatusEffect.Load(statusEffectElement, characterTalent.DebugIdentifier);
|
||||
statusEffects.Add(statusEffect);
|
||||
}
|
||||
|
||||
return statusEffects;
|
||||
}
|
||||
|
||||
public static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
|
||||
{
|
||||
StatTypes statType;
|
||||
if (!Enum.TryParse(statTypeString, true, out statType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
|
||||
}
|
||||
return statType;
|
||||
}
|
||||
|
||||
public static List<Affliction> ParseAfflictions(CharacterTalent characterTalent, XElement afflictionElements)
|
||||
{
|
||||
if (afflictionElements == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Affliction> afflictions = new List<Affliction>();
|
||||
|
||||
// similar logic to affliction creation in statuseffects
|
||||
// might be worth unifying
|
||||
|
||||
foreach (XElement afflictionElement in afflictionElements.Elements())
|
||||
{
|
||||
string afflictionIdentifier = afflictionElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Affliction afflictionInstance = afflictionPrefab.Instantiate(afflictionElement.GetAttributeFloat(1.0f, "amount", "strength"));
|
||||
afflictionInstance.Probability = afflictionElement.GetAttributeFloat(1.0f, "probability");
|
||||
afflictions.Add(afflictionInstance);
|
||||
}
|
||||
|
||||
return afflictions;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupEffect : CharacterAbilityGroup
|
||||
{
|
||||
public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { }
|
||||
|
||||
public void CheckAbilityGroup(object abilityData)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
if (IsApplicable(abilityData))
|
||||
{
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
if (characterAbility.IsViable())
|
||||
{
|
||||
characterAbility.ApplyAbilityEffect(abilityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsApplicable(object abilityData)
|
||||
{
|
||||
return abilityConditions.All(c => c.MatchesCondition(abilityData));
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupInterval : CharacterAbilityGroup
|
||||
{
|
||||
private float interval { get; set; }
|
||||
public float TimeSinceLastUpdate { get; private set; }
|
||||
|
||||
private float effectDelay;
|
||||
private float effectDelayTimer;
|
||||
|
||||
public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup)
|
||||
{
|
||||
// too many overlapping intervals could cause hitching? maybe randomize a little
|
||||
interval = abilityElementGroup.GetAttributeFloat("interval", 0f);
|
||||
effectDelay = abilityElementGroup.GetAttributeFloat("effectdelay", 0f);
|
||||
}
|
||||
public void UpdateAbilityGroup(float deltaTime)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
TimeSinceLastUpdate += deltaTime;
|
||||
if (TimeSinceLastUpdate >= interval)
|
||||
{
|
||||
bool conditionsMatched = IsApplicable();
|
||||
effectDelayTimer = conditionsMatched ? effectDelayTimer + TimeSinceLastUpdate : 0f;
|
||||
conditionsMatched &= effectDelayTimer >= effectDelay;
|
||||
|
||||
foreach (var characterAbility in characterAbilities)
|
||||
{
|
||||
if (characterAbility.IsViable())
|
||||
{
|
||||
characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
|
||||
}
|
||||
}
|
||||
TimeSinceLastUpdate = 0;
|
||||
}
|
||||
}
|
||||
private bool IsApplicable()
|
||||
{
|
||||
return abilityConditions.All(c => c.MatchesCondition());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterTalent
|
||||
{
|
||||
public Character Character { get; }
|
||||
public string DebugIdentifier { get; }
|
||||
|
||||
public readonly TalentPrefab Prefab;
|
||||
|
||||
private readonly Dictionary<AbilityEffectType, List<CharacterAbilityGroupEffect>> characterAbilityGroupEffectDictionary = new Dictionary<AbilityEffectType, List<CharacterAbilityGroupEffect>>();
|
||||
|
||||
private readonly List<CharacterAbilityGroupInterval> characterAbilityGroupIntervals = new List<CharacterAbilityGroupInterval>();
|
||||
|
||||
// works functionally but a missing recipe is not represented on GUI side. this might be better placed in the character class itself, though it might be fine here as well
|
||||
public List<string> UnlockedRecipes { get; } = new List<string>();
|
||||
|
||||
public CharacterTalent(TalentPrefab talentPrefab, Character character)
|
||||
{
|
||||
Character = character;
|
||||
|
||||
Prefab = talentPrefab;
|
||||
XElement element = talentPrefab.ConfigElement;
|
||||
DebugIdentifier = talentPrefab.OriginalName;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "abilitygroupeffect":
|
||||
LoadAbilityGroupEffect(subElement);
|
||||
break;
|
||||
case "abilitygroupinterval":
|
||||
LoadAbilityGroupInterval(subElement);
|
||||
break;
|
||||
case "addedrecipe":
|
||||
if (subElement.GetAttributeString("itemidentifier", string.Empty) is string recipeIdentifier && recipeIdentifier != string.Empty)
|
||||
{
|
||||
UnlockedRecipes.Add(recipeIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("No recipe identifier defined for talent " + DebugIdentifier);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateTalent(float deltaTime)
|
||||
{
|
||||
foreach (var characterAbilityGroupInterval in characterAbilityGroupIntervals)
|
||||
{
|
||||
characterAbilityGroupInterval.UpdateAbilityGroup(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckTalent(AbilityEffectType abilityEffectType, object abilityData)
|
||||
{
|
||||
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups))
|
||||
{
|
||||
foreach (var characterAbilityGroup in characterAbilityGroups)
|
||||
{
|
||||
characterAbilityGroup.CheckAbilityGroup(abilityData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateTalent(bool addingFirstTime)
|
||||
{
|
||||
foreach (var characterAbilityGroups in characterAbilityGroupEffectDictionary.Values)
|
||||
{
|
||||
foreach (var characterAbilityGroup in characterAbilityGroups)
|
||||
{
|
||||
characterAbilityGroup.ActivateAbilityGroup(addingFirstTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XML logic
|
||||
private void LoadAbilityGroupInterval(XElement abilityGroup)
|
||||
{
|
||||
string name = abilityGroup.Name.ToString().ToLowerInvariant();
|
||||
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, abilityGroup));
|
||||
}
|
||||
|
||||
private void LoadAbilityGroupEffect(XElement abilityGroup)
|
||||
{
|
||||
AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none"));
|
||||
AddAbilityGroupEffect(new CharacterAbilityGroupEffect(this, abilityGroup), abilityEffectType);
|
||||
}
|
||||
|
||||
public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None)
|
||||
{
|
||||
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityList))
|
||||
{
|
||||
characterAbilityList.Add(characterAbilityGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<CharacterAbilityGroupEffect> characterAbilityGroups = new List<CharacterAbilityGroupEffect>();
|
||||
characterAbilityGroups.Add(characterAbilityGroup);
|
||||
characterAbilityGroupEffectDictionary.Add(abilityEffectType, characterAbilityGroups);
|
||||
}
|
||||
}
|
||||
|
||||
public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString)
|
||||
{
|
||||
AbilityEffectType abilityEffectType = AbilityEffectType.Undefined;
|
||||
if (!Enum.TryParse(abilityEffectTypeString, true, out abilityEffectType))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
|
||||
}
|
||||
if (abilityEffectType == AbilityEffectType.Undefined)
|
||||
{
|
||||
DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
|
||||
}
|
||||
|
||||
return abilityEffectType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TalentPrefab : IPrefab, IDisposable, IHasUintIdentifier
|
||||
{
|
||||
public string Identifier { get; private set; }
|
||||
public string OriginalName => Identifier;
|
||||
public ContentPackage ContentPackage { get; private set; }
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
public static readonly PrefabCollection<TalentPrefab> TalentPrefabs = new PrefabCollection<TalentPrefab>();
|
||||
|
||||
public XElement ConfigElement
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TalentPrefab(XElement element, string filePath)
|
||||
{
|
||||
FilePath = filePath;
|
||||
ConfigElement = element;
|
||||
Identifier = element.GetAttributeString("identifier", "noidentifier");
|
||||
this.CalculatePrefabUIntIdentifier(TalentPrefabs);
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
TalentPrefabs.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier that's generated by hashing the prefab's string identifier.
|
||||
/// Used to reduce the amount of bytes needed to write talent data into network messages in multiplayer.
|
||||
/// </summary>
|
||||
public uint UIntIdentifier { get; set; }
|
||||
|
||||
public static void RemoveByFile(string filePath) => TalentPrefabs.RemoveByFile(filePath);
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
DebugConsole.Log("Loading talent prefab: " + file.Path);
|
||||
RemoveByFile(file.Path);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root;
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "talent":
|
||||
TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), false);
|
||||
break;
|
||||
case "talents":
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var itemElement = element.GetChildElement("talent");
|
||||
if (itemElement != null)
|
||||
{
|
||||
TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a talent element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TalentPrefabs.Add(new TalentPrefab(element, file.Path), false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
DebugConsole.Log("Loading talent prefabs: ");
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
LoadFromFile(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TalentTree
|
||||
{
|
||||
public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>();
|
||||
|
||||
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
|
||||
|
||||
private static HashSet<string> subtreeTalents = new HashSet<string>();
|
||||
|
||||
public XElement ConfigElement
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TalentTree(XElement element, string filePath)
|
||||
{
|
||||
ConfigElement = element;
|
||||
|
||||
string jobIdentifier = element.GetAttributeString("jobidentifier", "");
|
||||
|
||||
if (string.IsNullOrEmpty(jobIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("No job defined for talent tree!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subTreeElement in element.GetChildElements("subtree"))
|
||||
{
|
||||
TalentSubTrees.Add(new TalentSubTree(subTreeElement));
|
||||
}
|
||||
|
||||
// talents found and unlocked using the identifier wihin the talent tree, so no duplicates may occur
|
||||
HashSet<string> duplicateSet = new HashSet<string>();
|
||||
foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))))
|
||||
{
|
||||
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
|
||||
if (talentPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Talent tree for job {jobIdentifier} contains non-existent talent {talent}! Talent tree not added.");
|
||||
return;
|
||||
}
|
||||
if (!duplicateSet.Add(talent))
|
||||
{
|
||||
DebugConsole.ThrowError($"Talent tree for job {jobIdentifier} contains duplicate talent {talent}! Talent tree not added.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!JobTalentTrees.TryAdd(jobIdentifier, this))
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not add talent tree for job {jobIdentifier}! A talent tree for this job is already likely defined");
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
DebugConsole.Log("Loading talent tree: " + file.Path);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root;
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "talenttree":
|
||||
new TalentTree(rootElement, file.Path);
|
||||
break;
|
||||
case "talenttrees":
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var treeElement = element.GetChildElement("talenttree");
|
||||
if (treeElement != null)
|
||||
{
|
||||
new TalentTree(rootElement, file.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a talent tree element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
new TalentTree(element, file.Path);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
DebugConsole.Log("Loading talent tree: ");
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
LoadFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier)
|
||||
{
|
||||
return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
|
||||
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable<string> selectedTalents)
|
||||
{
|
||||
if (character?.Info?.Job.Prefab == null) { return false; }
|
||||
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; }
|
||||
|
||||
if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
|
||||
|
||||
foreach (var subTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
foreach (var talentOptionStage in subTree.TalentOptionStages)
|
||||
{
|
||||
bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier));
|
||||
if (!hasTalentInThisTier)
|
||||
{
|
||||
if (talentOptionStage.Talents.Any(t => t.Identifier == talentIdentifier))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<string> CheckTalentSelection(Character controlledCharacter, IEnumerable<string> selectedTalents)
|
||||
{
|
||||
List<string> viableTalents = new List<string>();
|
||||
bool canStillUnlock = true;
|
||||
// keep trying to unlock talents until none of the talents are unlockable
|
||||
while (canStillUnlock && selectedTalents.Any())
|
||||
{
|
||||
canStillUnlock = false;
|
||||
foreach (string talent in selectedTalents)
|
||||
{
|
||||
if (IsViableTalentForCharacter(controlledCharacter, talent, viableTalents))
|
||||
{
|
||||
viableTalents.Add(talent);
|
||||
canStillUnlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return viableTalents;
|
||||
}
|
||||
}
|
||||
|
||||
class TalentSubTree
|
||||
{
|
||||
public string Identifier { get; }
|
||||
|
||||
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
|
||||
|
||||
public TalentSubTree(XElement subTreeElement)
|
||||
{
|
||||
Identifier = subTreeElement.GetAttributeString("identifier", "");
|
||||
|
||||
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
|
||||
{
|
||||
TalentOptionStages.Add(new TalentOption(talentOptionsElement));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TalentOption
|
||||
{
|
||||
public readonly List<Talent> Talents = new List<Talent>();
|
||||
|
||||
public TalentOption(XElement talentOptionsElement)
|
||||
{
|
||||
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
|
||||
{
|
||||
Talents.Add(new Talent(talentOptionElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Talent
|
||||
{
|
||||
public readonly string Identifier;
|
||||
public readonly Sprite Icon;
|
||||
public Talent(XElement talentOptionElement)
|
||||
{
|
||||
Identifier = talentOptionElement.GetAttributeString("identifier", "");
|
||||
foreach (XElement subElement in talentOptionElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "icon":
|
||||
Icon = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@ namespace Barotrauma
|
||||
WreckAIConfig,
|
||||
UpgradeModules,
|
||||
MapCreature,
|
||||
EnemySubmarine
|
||||
EnemySubmarine,
|
||||
Talents,
|
||||
TalentTrees,
|
||||
}
|
||||
|
||||
public class ContentPackage
|
||||
@@ -103,7 +105,8 @@ namespace Barotrauma
|
||||
ContentType.Corpses,
|
||||
ContentType.UpgradeModules,
|
||||
ContentType.MapCreature,
|
||||
ContentType.EnemySubmarine
|
||||
ContentType.EnemySubmarine,
|
||||
ContentType.Talents,
|
||||
};
|
||||
|
||||
//at least one file of each these types is required in core content packages
|
||||
@@ -135,7 +138,8 @@ namespace Barotrauma
|
||||
ContentType.Orders,
|
||||
ContentType.Corpses,
|
||||
ContentType.UpgradeModules,
|
||||
ContentType.EnemySubmarine
|
||||
ContentType.EnemySubmarine,
|
||||
ContentType.Talents,
|
||||
};
|
||||
|
||||
public static IEnumerable<ContentType> CorePackageRequiredFiles
|
||||
|
||||
@@ -191,11 +191,6 @@ namespace Barotrauma
|
||||
GameMain.NetworkMember.ShowNetStats = !GameMain.NetworkMember.ShowNetStats;
|
||||
}));
|
||||
|
||||
commands.Add(new Command("createfilelist", "", (string[] args) =>
|
||||
{
|
||||
UpdaterUtil.SaveFileList("filelist.xml");
|
||||
}));
|
||||
|
||||
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team (0-3)]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
|
||||
() =>
|
||||
{
|
||||
@@ -580,7 +575,7 @@ namespace Barotrauma
|
||||
}
|
||||
}));
|
||||
|
||||
commands.Add(new Command("dumptofile", "", (string[] args) =>
|
||||
commands.Add(new Command("dumptofile", "findentityids [filename]: Outputs the contents of the debug console into a text file in the game folder. If the filename argument is omitted, \"consoleOutput.txt\" is used as the filename.", (string[] args) =>
|
||||
{
|
||||
string filename = "consoleOutput.txt";
|
||||
if (args.Length > 0) { filename = string.Join(" ", args); }
|
||||
@@ -607,7 +602,7 @@ namespace Barotrauma
|
||||
}
|
||||
}));
|
||||
|
||||
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
|
||||
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 2) { return; }
|
||||
|
||||
@@ -632,14 +627,21 @@ namespace Barotrauma
|
||||
bool.TryParse(args[2], out relativeStrength);
|
||||
}
|
||||
|
||||
Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray());
|
||||
Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] });
|
||||
|
||||
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
Limb targetLimb = targetCharacter.AnimController.MainLimb;
|
||||
if (args.Length > 3)
|
||||
{
|
||||
targetLimb = targetCharacter.AnimController.Limbs.FirstOrDefault(l => l.type.ToString().Equals(args[3], StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
if (relativeStrength)
|
||||
{
|
||||
afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength;
|
||||
}
|
||||
targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength));
|
||||
targetCharacter.CharacterHealth.ApplyAffliction(targetLimb ?? targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength));
|
||||
}
|
||||
},
|
||||
() =>
|
||||
@@ -648,7 +650,8 @@ namespace Barotrauma
|
||||
{
|
||||
AfflictionPrefab.List.Select(a => a.Name).ToArray(),
|
||||
new string[] { "1" },
|
||||
Character.CharacterList.Select(c => c.Name).ToArray()
|
||||
Character.CharacterList.Select(c => c.Name).ToArray(),
|
||||
Enum.GetNames(typeof(LimbType)).ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -833,9 +836,68 @@ namespace Barotrauma
|
||||
commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) =>
|
||||
{
|
||||
Hull.EditWater = !Hull.EditWater;
|
||||
NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White);
|
||||
NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("givetalent", "give [player] testing [talent]", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 2) return;
|
||||
var character = FindMatchingCharacter(args.Skip(1).ToArray()) ?? Character.Controlled;
|
||||
if (character != null)
|
||||
{
|
||||
character.GiveTalent(args[0]);
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
List<string> talentNames = new List<string>();
|
||||
foreach (TalentPrefab itemPrefab in TalentPrefab.TalentPrefabs)
|
||||
{
|
||||
talentNames.Add(itemPrefab.Identifier);
|
||||
}
|
||||
|
||||
return new string[][]
|
||||
{
|
||||
talentNames.ToArray(),
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("giveexperience", "giveexperience [amount] [character]: Give experience to character.", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
NewMessage($"Missing arguments. Expected at least 1 but got {args.Length} (experience, name)");
|
||||
return;
|
||||
}
|
||||
|
||||
string experienceString = args[0];
|
||||
var character = FindMatchingCharacter(args.Skip(1).ToArray()) ?? Character.Controlled;
|
||||
|
||||
if (character?.Info == null)
|
||||
{
|
||||
NewMessage("Character is not valid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (int.TryParse(experienceString, NumberStyles.Number, CultureInfo.InvariantCulture, out int experience))
|
||||
{
|
||||
character.Info.GiveExperience(experience);
|
||||
NewMessage($"Gave {character.Name} {experience} experience");
|
||||
}
|
||||
else
|
||||
{
|
||||
NewMessage($"{experienceString} is not a valid value. Expected number.");
|
||||
}
|
||||
}, isCheat: true, getValidArgs: () =>
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new string[] { "100" },
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray(),
|
||||
};
|
||||
}));
|
||||
|
||||
commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) =>
|
||||
{
|
||||
Hull.EditFire = !Hull.EditFire;
|
||||
@@ -947,7 +1009,7 @@ namespace Barotrauma
|
||||
string subName = GameMain.Config.QuickStartSubmarineName;
|
||||
if (!string.IsNullOrEmpty(subName))
|
||||
{
|
||||
selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.ToLower() == subName.ToLower());
|
||||
selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.Equals(subName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
@@ -1013,7 +1075,7 @@ namespace Barotrauma
|
||||
if (args.Length == 0) { return; }
|
||||
if (float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation))
|
||||
{
|
||||
campaign.Map.CurrentLocation.Reputation.Value = reputation;
|
||||
campaign.Map.CurrentLocation.Reputation.SetReputation(reputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1040,7 +1102,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (float.TryParse(args[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation))
|
||||
{
|
||||
faction.Reputation.Value = reputation;
|
||||
faction.Reputation.SetReputation(reputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1368,7 +1430,7 @@ namespace Barotrauma
|
||||
NewMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", Color.White);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("money", "", args =>
|
||||
commands.Add(new Command("money", "money [amount]: Gives the specified amount of money to the crew when a campaign is active.", args =>
|
||||
{
|
||||
if (args.Length == 0) { return; }
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
@@ -1488,8 +1550,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
string packageName = string.Join(" ", args).ToLower();
|
||||
var package = GameMain.Config.AllEnabledPackages.FirstOrDefault(p => p.Name.ToLower() == packageName);
|
||||
string packageName = string.Join(" ", args);
|
||||
var package = GameMain.Config.AllEnabledPackages.FirstOrDefault(p => p.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase));
|
||||
if (package == null)
|
||||
{
|
||||
ThrowError("Content package \"" + packageName + "\" not found.");
|
||||
|
||||
@@ -23,5 +23,78 @@
|
||||
OnProduceSpawned,
|
||||
OnOpen, OnClose,
|
||||
OnDeath = OnBroken,
|
||||
OnSuccess,
|
||||
OnAbility,
|
||||
}
|
||||
|
||||
public enum AbilityEffectType
|
||||
{
|
||||
Undefined,
|
||||
None,
|
||||
OnAttack,
|
||||
OnAttackResult,
|
||||
OnAttacked,
|
||||
OnGainSkillPoint,
|
||||
OnAllyGainSkillPoint,
|
||||
OnRepairComplete,
|
||||
OnItemFabricationSkillGain,
|
||||
OnItemFabricatedAmount,
|
||||
OnAllyItemFabricatedAmount,
|
||||
OnOpenItemContainer,
|
||||
OnUseRangedWeapon,
|
||||
OnReduceAffliction,
|
||||
OnAddDamageAffliction,
|
||||
OnSelfRagdoll,
|
||||
OnAnyMissionCompleted,
|
||||
OnAllMissionsCompleted,
|
||||
OnGiveOrder,
|
||||
OnCrewKillCharacter,
|
||||
OnDieToCharacter,
|
||||
OnAllyGainMissionExperience,
|
||||
OnGainMissionExperience,
|
||||
OnGainMissionMoney,
|
||||
AfterSubmarineAttacked,
|
||||
}
|
||||
|
||||
public enum StatTypes
|
||||
{
|
||||
None,
|
||||
// Skills
|
||||
ElectricalSkillBonus,
|
||||
HelmSkillBonus,
|
||||
MechanicalSkillBonus,
|
||||
MedicalSkillBonus,
|
||||
WeaponsSkillBonus,
|
||||
// Character attributes
|
||||
MaximumHealthMultiplier,
|
||||
MovementSpeed,
|
||||
SwimmingSpeed,
|
||||
BuffDurationMultiplier,
|
||||
DebuffDurationMultiplier,
|
||||
// Combat
|
||||
AttackMultiplier,
|
||||
RangedAttackSpeed,
|
||||
TurretAttackSpeed,
|
||||
MeleeAttackSpeed,
|
||||
SpreadMultiplier,
|
||||
// Utility
|
||||
RepairSpeed,
|
||||
// Misc
|
||||
ReputationGainMultiplier,
|
||||
MissionMoneyGainMultiplier,
|
||||
ExperienceGainMultiplier,
|
||||
MissionExperienceGainMultiplier,
|
||||
|
||||
}
|
||||
|
||||
public enum AbilityFlags
|
||||
{
|
||||
None,
|
||||
MustWalk,
|
||||
ImmuneToPressure,
|
||||
IgnoredByEnemyAI,
|
||||
MoveNormallyWhileDragging,
|
||||
CanTinker,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (faction != null)
|
||||
{
|
||||
faction.Reputation.Value += Increase;
|
||||
faction.Reputation.AddReputation(Increase);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -64,14 +64,14 @@ namespace Barotrauma
|
||||
Location location = campaign.Map.CurrentLocation;
|
||||
if (location != null)
|
||||
{
|
||||
location.Reputation.Value += Increase;
|
||||
location.Reputation.AddReputation(Increase);
|
||||
IEnumerable<Location> locations = location.Connections.SelectMany(c => c.Locations).Distinct().Where(l => l != null && l != location);
|
||||
foreach (Location connectedLocation in locations)
|
||||
{
|
||||
Debug.Assert(connectedLocation.Reputation != null, "connectedLocation.Reputation != null");
|
||||
if (connectedLocation.Reputation != null)
|
||||
{
|
||||
connectedLocation.Reputation.Value += (Increase / 4);
|
||||
connectedLocation.Reputation.AddReputation(Increase / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,9 +380,13 @@ namespace Barotrauma
|
||||
{
|
||||
pendingEventSets.Clear();
|
||||
selectedEvents.Clear();
|
||||
activeEvents.Clear();
|
||||
QueuedEvents.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
|
||||
pathFinder = null;
|
||||
}
|
||||
|
||||
private float CalculateCommonness(EventPrefab eventPrefab, float baseCommonness)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -343,19 +345,40 @@ namespace Barotrauma
|
||||
public void GiveReward()
|
||||
{
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; }
|
||||
campaign.Money += GetReward(Submarine.MainSub);
|
||||
int reward = GetReward(Submarine.MainSub);
|
||||
|
||||
float baseExperienceGain = reward * 0.15f;
|
||||
|
||||
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);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
|
||||
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
|
||||
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
character.Info.GiveExperience((int)(baseExperienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
|
||||
}
|
||||
|
||||
// apply money gains afterwards to prevent them from affecting XP gains
|
||||
var moneyGainMultiplier = new AbilityValue(1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, (this, moneyGainMultiplier)));
|
||||
crewCharacters.ForEach(c => moneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
|
||||
campaign.Money += (int)(reward * moneyGainMultiplier.Value);
|
||||
|
||||
foreach (KeyValuePair<string, float> reputationReward in ReputationRewards)
|
||||
{
|
||||
if (reputationReward.Key.Equals("location", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Locations[0].Reputation.Value += reputationReward.Value;
|
||||
Locations[1].Reputation.Value += reputationReward.Value;
|
||||
Locations[0].Reputation.AddReputation(reputationReward.Value);
|
||||
Locations[1].Reputation.AddReputation(reputationReward.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(reputationReward.Key, StringComparison.OrdinalIgnoreCase));
|
||||
if (faction != null) { faction.Reputation.Value += reputationReward.Value; }
|
||||
if (faction != null) { faction.Reputation.AddReputation(reputationReward.Value); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ namespace Barotrauma
|
||||
{
|
||||
static class AutoItemPlacer
|
||||
{
|
||||
private static readonly List<Item> spawnedItems = new List<Item>();
|
||||
|
||||
public static bool OutputDebugInfo = false;
|
||||
|
||||
public static void PlaceIfNeeded()
|
||||
@@ -41,7 +39,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static void Place(IEnumerable<Submarine> subs)
|
||||
public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer)
|
||||
{
|
||||
Place(sub.ToEnumerable(), regeneratedContainer);
|
||||
}
|
||||
|
||||
private static void Place(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -49,19 +52,29 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
List<Item> spawnedItems = new List<Item>(100);
|
||||
|
||||
int itemCountApprox = MapEntityPrefab.List.Count() / 3;
|
||||
var containers = new List<ItemContainer>(70 + 30 * subs.Count());
|
||||
var prefabsWithContainer = new List<ItemPrefab>(itemCountApprox / 3);
|
||||
var prefabsWithoutContainer = new List<ItemPrefab>(itemCountApprox);
|
||||
var removals = new List<ItemPrefab>();
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
// generate loot only for a specific container if defined
|
||||
if (regeneratedContainer != null)
|
||||
{
|
||||
if (!subs.Contains(item.Submarine)) { continue; }
|
||||
if (item.GetRootInventoryOwner() is Character) { continue; }
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
containers.Add(regeneratedContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!subs.Contains(item.Submarine)) { continue; }
|
||||
if (item.GetRootInventoryOwner() is Character) { continue; }
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
}
|
||||
containers.Shuffle(Rand.RandSync.Server);
|
||||
}
|
||||
containers.Shuffle(Rand.RandSync.Server);
|
||||
|
||||
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
|
||||
{
|
||||
@@ -77,7 +90,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
spawnedItems.Clear();
|
||||
var validContainers = new Dictionary<ItemContainer, PreferredContainer>();
|
||||
prefabsWithContainer.Shuffle(Rand.RandSync.Server);
|
||||
// Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc)
|
||||
@@ -152,8 +164,10 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (var validContainer in validContainers)
|
||||
{
|
||||
if (SpawnItem(itemPrefab, containers, validContainer))
|
||||
var newItems = SpawnItem(itemPrefab, containers, validContainer);
|
||||
if (newItems.Any())
|
||||
{
|
||||
spawnedItems.AddRange(newItems);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -184,14 +198,15 @@ namespace Barotrauma
|
||||
return validContainers;
|
||||
}
|
||||
|
||||
private static bool SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
{
|
||||
List<Item> spawnedItems = new List<Item>();
|
||||
bool success = false;
|
||||
if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return false; }
|
||||
if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return spawnedItems; }
|
||||
// Don't add dangerously reactive materials in thalamus wrecks
|
||||
if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
|
||||
{
|
||||
return false;
|
||||
return spawnedItems;
|
||||
}
|
||||
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.Server);
|
||||
for (int i = 0; i < amount; i++)
|
||||
@@ -219,7 +234,7 @@ namespace Barotrauma
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
return spawnedItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,9 @@ namespace Barotrauma
|
||||
{
|
||||
CharacterInfo.ApplyHealthData(character, character.Info.HealthData);
|
||||
}
|
||||
|
||||
character.LoadTalents();
|
||||
|
||||
character.GiveIdCardTags(spawnWaypoints[i]);
|
||||
character.Info.StartItemsGiven = true;
|
||||
if (character.Info.OrderData != null)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -31,7 +32,7 @@ namespace Barotrauma
|
||||
public float Value
|
||||
{
|
||||
get => Math.Min(MaxReputation, Metadata.GetFloat(metaDataIdentifier, InitialReputation));
|
||||
set
|
||||
private set
|
||||
{
|
||||
if (MathUtils.NearlyEqual(Value, value)) { return; }
|
||||
Metadata.SetValue(metaDataIdentifier, Math.Clamp(value, MinReputation, MaxReputation));
|
||||
@@ -40,6 +41,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReputation(float newReputation)
|
||||
{
|
||||
Value = newReputation;
|
||||
}
|
||||
|
||||
public void AddReputation(float reputationChange)
|
||||
{
|
||||
if (reputationChange > 0f)
|
||||
{
|
||||
float reputationGainMultiplier = 1f;
|
||||
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.Team1))
|
||||
{
|
||||
reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier);
|
||||
}
|
||||
reputationChange *= reputationGainMultiplier;
|
||||
}
|
||||
Value += reputationChange;
|
||||
}
|
||||
|
||||
public Action OnReputationValueChanged;
|
||||
public static Action OnAnyReputationValueChanged;
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ namespace Barotrauma
|
||||
public static CampaignSettings Unsure = Empty;
|
||||
public bool RadiationEnabled { get; set; }
|
||||
public int MaxMissionCount { get; set; }
|
||||
public int AddedMissionCount { get; set; }
|
||||
|
||||
public int TotalMaxMissionCount => MaxMissionCount + AddedMissionCount;
|
||||
|
||||
|
||||
public const int DefaultMaxMissionCount = 2;
|
||||
public const int MaxMissionCountLimit = 10;
|
||||
@@ -27,23 +31,26 @@ namespace Barotrauma
|
||||
{
|
||||
RadiationEnabled = inc.ReadBoolean();
|
||||
MaxMissionCount = inc.ReadInt32();
|
||||
AddedMissionCount = inc.ReadInt32();
|
||||
}
|
||||
|
||||
|
||||
public CampaignSettings(XElement element)
|
||||
{
|
||||
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLower(), true);
|
||||
MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLower(), DefaultMaxMissionCount);
|
||||
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true);
|
||||
MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount);
|
||||
AddedMissionCount = element.GetAttributeInt(nameof(AddedMissionCount).ToLowerInvariant(), 0);
|
||||
}
|
||||
|
||||
public void Serialize(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(RadiationEnabled);
|
||||
msg.Write(MaxMissionCount);
|
||||
msg.Write(AddedMissionCount);
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLower(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLower().ToLower(), MaxMissionCount));
|
||||
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount), new XAttribute(nameof(AddedMissionCount).ToLowerInvariant(), AddedMissionCount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +233,8 @@ namespace Barotrauma
|
||||
PurchasedLostShuttles = false;
|
||||
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
|
||||
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
|
||||
|
||||
ResetTalentData();
|
||||
}
|
||||
|
||||
public void InitCampaignData()
|
||||
@@ -846,7 +855,7 @@ namespace Barotrauma
|
||||
Location location = Map?.CurrentLocation;
|
||||
if (location != null)
|
||||
{
|
||||
location.Reputation.Value -= attackResult.Damage * Reputation.ReputationLossPerNPCDamage;
|
||||
location.Reputation.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,15 +907,24 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Location location in currentLocation.Connections.Select(c => c.OtherLocation(currentLocation)))
|
||||
{
|
||||
if (NumberOfMissionsAtLocation(location) > Settings.MaxMissionCount)
|
||||
if (NumberOfMissionsAtLocation(location) > Settings.TotalMaxMissionCount)
|
||||
{
|
||||
DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.Name}! Count was {NumberOfMissionsAtLocation(location)}. Deselecting extra missions.");
|
||||
foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.MaxMissionCount).ToList())
|
||||
foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.TotalMaxMissionCount).ToList())
|
||||
{
|
||||
currentLocation.DeselectMission(mission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Talent relevant data, only stored for the duration of the mission
|
||||
private void ResetTalentData()
|
||||
{
|
||||
CrewHasDied = false;
|
||||
}
|
||||
|
||||
public bool CrewHasDied { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Barotrauma
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
public bool RoundEnding { get; private set; }
|
||||
|
||||
public Level Level { get; private set; }
|
||||
public LevelData LevelData { get; private set; }
|
||||
|
||||
@@ -654,43 +656,83 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
public static IEnumerable<Character> GetSessionCrewCharacters()
|
||||
{
|
||||
#if SERVER
|
||||
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c.Info != null);
|
||||
#else
|
||||
return GameMain.GameSession.CrewManager.CharacterInfos.Select(i => i.Character).Where(c => c != null);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void EndRound(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
|
||||
{
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
mission.End();
|
||||
}
|
||||
#if CLIENT
|
||||
if (GUI.PauseMenuOpen)
|
||||
{
|
||||
GUI.TogglePauseMenu();
|
||||
}
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
RoundEnding = true;
|
||||
|
||||
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
|
||||
try
|
||||
{
|
||||
GUI.ClearMessages();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
|
||||
GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType);
|
||||
GUIMessageBox.MessageBoxes.Add(summaryFrame);
|
||||
RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
|
||||
}
|
||||
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters();
|
||||
|
||||
if (GameMain.NetLobbyScreen != null) GameMain.NetLobbyScreen.OnRoundEnded();
|
||||
TabMenu.OnRoundEnded();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
|
||||
#endif
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
mission.End();
|
||||
}
|
||||
|
||||
GameMode?.End(transitionType);
|
||||
EventManager?.EndRound();
|
||||
StatusEffect.StopAll();
|
||||
missions.Clear();
|
||||
IsRunning = false;
|
||||
if (missions.Any())
|
||||
{
|
||||
if (missions.Any(m => m.Completed))
|
||||
{
|
||||
foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos)
|
||||
{
|
||||
characterInfo.Character?.CheckTalents(AbilityEffectType.OnAnyMissionCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
if (missions.All(m => m.Completed))
|
||||
{
|
||||
foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos)
|
||||
{
|
||||
characterInfo.Character?.CheckTalents(AbilityEffectType.OnAllMissionsCompleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
HintManager.OnRoundEnded();
|
||||
if (GUI.PauseMenuOpen)
|
||||
{
|
||||
GUI.TogglePauseMenu();
|
||||
}
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
|
||||
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
|
||||
{
|
||||
GUI.ClearMessages();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
|
||||
GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType);
|
||||
GUIMessageBox.MessageBoxes.Add(summaryFrame);
|
||||
RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
|
||||
}
|
||||
|
||||
if (GameMain.NetLobbyScreen != null) { GameMain.NetLobbyScreen.OnRoundEnded(); }
|
||||
TabMenu.OnRoundEnded();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
|
||||
#endif
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
|
||||
GameMode?.End(transitionType);
|
||||
EventManager?.EndRound();
|
||||
StatusEffect.StopAll();
|
||||
missions.Clear();
|
||||
IsRunning = false;
|
||||
|
||||
#if CLIENT
|
||||
HintManager.OnRoundEnded();
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
RoundEnding = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void KillCharacter(Character character)
|
||||
|
||||
@@ -307,6 +307,7 @@ namespace Barotrauma
|
||||
public bool AutomaticQuickStartEnabled { get; set; }
|
||||
public bool AutomaticCampaignLoadEnabled { get; set; }
|
||||
public bool TextManagerDebugModeEnabled { get; set; }
|
||||
public bool TestScreenEnabled { get; set; }
|
||||
|
||||
public bool ModBreakerMode { get; set; }
|
||||
#endif
|
||||
@@ -548,6 +549,12 @@ namespace Barotrauma
|
||||
case ContentType.Text:
|
||||
TextManager.LoadTextPack(file.Path);
|
||||
break;
|
||||
case ContentType.Talents:
|
||||
TalentPrefab.LoadFromFile(file);
|
||||
break;
|
||||
case ContentType.TalentTrees:
|
||||
TalentTree.LoadFromFile(file);
|
||||
break;
|
||||
#if CLIENT
|
||||
case ContentType.Particles:
|
||||
GameMain.ParticleManager?.LoadPrefabsFromFile(file);
|
||||
@@ -594,6 +601,12 @@ namespace Barotrauma
|
||||
case ContentType.Text:
|
||||
TextManager.RemoveTextPack(file.Path);
|
||||
break;
|
||||
case ContentType.Talents:
|
||||
TalentPrefab.LoadFromFile(file);
|
||||
break;
|
||||
case ContentType.TalentTrees:
|
||||
TalentTree.LoadFromFile(file);
|
||||
break;
|
||||
#if CLIENT
|
||||
case ContentType.Particles:
|
||||
GameMain.ParticleManager?.RemovePrefabsByFile(file.Path);
|
||||
@@ -703,7 +716,6 @@ namespace Barotrauma
|
||||
public string MasterServerUrl { get; set; }
|
||||
public string RemoteContentUrl { get; set; }
|
||||
public bool AutoCheckUpdates { get; set; }
|
||||
public bool WasGameUpdated { get; set; }
|
||||
|
||||
private string playerName;
|
||||
public string PlayerName
|
||||
@@ -796,13 +808,6 @@ namespace Barotrauma
|
||||
|
||||
LoadDefaultConfig();
|
||||
|
||||
if (WasGameUpdated)
|
||||
{
|
||||
UpdaterUtil.CleanOldFiles();
|
||||
WasGameUpdated = false;
|
||||
SaveNewDefaultConfig();
|
||||
}
|
||||
|
||||
LoadPlayerConfig();
|
||||
}
|
||||
|
||||
@@ -827,7 +832,6 @@ namespace Barotrauma
|
||||
|
||||
MasterServerUrl = doc.Root.GetAttributeString("masterserverurl", MasterServerUrl);
|
||||
RemoteContentUrl = doc.Root.GetAttributeString("remotecontenturl", RemoteContentUrl);
|
||||
WasGameUpdated = doc.Root.GetAttributeBool("wasgameupdated", WasGameUpdated);
|
||||
VerboseLogging = doc.Root.GetAttributeBool("verboselogging", VerboseLogging);
|
||||
SaveDebugConsoleLogs = doc.Root.GetAttributeBool("savedebugconsolelogs", SaveDebugConsoleLogs);
|
||||
AutoUpdateWorkshopItems = doc.Root.GetAttributeBool("autoupdateworkshopitems", AutoUpdateWorkshopItems);
|
||||
@@ -889,11 +893,6 @@ namespace Barotrauma
|
||||
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
|
||||
}
|
||||
|
||||
if (WasGameUpdated)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("wasgameupdated", true));
|
||||
}
|
||||
|
||||
XElement gMode = doc.Root.Element("graphicsmode");
|
||||
if (gMode == null)
|
||||
{
|
||||
@@ -1147,6 +1146,7 @@ namespace Barotrauma
|
||||
new XAttribute("disableingamehints", DisableInGameHints)
|
||||
#if DEBUG
|
||||
, new XAttribute("automaticquickstartenabled", AutomaticQuickStartEnabled)
|
||||
, new XAttribute(nameof(TestScreenEnabled).ToLower(), TestScreenEnabled)
|
||||
, new XAttribute("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled)
|
||||
, new XAttribute("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled)
|
||||
, new XAttribute("modbreakermode", ModBreakerMode)
|
||||
@@ -1402,6 +1402,7 @@ namespace Barotrauma
|
||||
DisableInGameHints = doc.Root.GetAttributeBool("disableingamehints", DisableInGameHints);
|
||||
#if DEBUG
|
||||
AutomaticQuickStartEnabled = doc.Root.GetAttributeBool("automaticquickstartenabled", AutomaticQuickStartEnabled);
|
||||
TestScreenEnabled = doc.Root.GetAttributeBool(nameof(TestScreenEnabled).ToLower(), TestScreenEnabled);
|
||||
AutomaticCampaignLoadEnabled = doc.Root.GetAttributeBool("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled);
|
||||
TextManagerDebugModeEnabled = doc.Root.GetAttributeBool("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled);
|
||||
ModBreakerMode = doc.Root.GetAttributeBool("modbreakermode", ModBreakerMode);
|
||||
@@ -1686,7 +1687,6 @@ namespace Barotrauma
|
||||
Language = "English";
|
||||
}
|
||||
MasterServerUrl = "http://www.undertowgames.com/baromaster";
|
||||
WasGameUpdated = false;
|
||||
VerboseLogging = false;
|
||||
SaveDebugConsoleLogs = false;
|
||||
AutoUpdateWorkshopItems = true;
|
||||
|
||||
@@ -204,8 +204,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
target.InitializeLinks();
|
||||
|
||||
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
|
||||
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
|
||||
if (!item.linkedTo.Contains(target.item)) { item.linkedTo.Add(target.item); }
|
||||
if (!target.item.linkedTo.Contains(item)) { target.item.linkedTo.Add(item); }
|
||||
|
||||
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
|
||||
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
|
||||
@@ -291,7 +291,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
|
||||
List<MapEntity> removedEntities = item.linkedTo.Where(e => e.Removed).ToList();
|
||||
foreach (MapEntity removed in removedEntities) item.linkedTo.Remove(removed);
|
||||
foreach (MapEntity removed in removedEntities) { item.linkedTo.Remove(removed); }
|
||||
|
||||
if (!item.linkedTo.Any(e => e is Hull) && !DockingTarget.item.linkedTo.Any(e => e is Hull))
|
||||
{
|
||||
@@ -306,9 +306,8 @@ namespace Barotrauma.Items.Components
|
||||
if (myWayPoint != null && targetWayPoint != null)
|
||||
{
|
||||
myWayPoint.FindHull();
|
||||
myWayPoint.linkedTo.Add(targetWayPoint);
|
||||
targetWayPoint.FindHull();
|
||||
targetWayPoint.linkedTo.Add(myWayPoint);
|
||||
myWayPoint.ConnectTo(targetWayPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,8 +596,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
hullRects[i].X -= expand;
|
||||
hullRects[i].Width += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
|
||||
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
|
||||
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
hulls[i].FreeID();
|
||||
|
||||
@@ -716,8 +716,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
hullRects[i].Y += expand;
|
||||
hullRects[i].Height += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
|
||||
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
|
||||
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
hulls[i].FreeID();
|
||||
|
||||
@@ -873,8 +874,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
myWayPoint.FindHull();
|
||||
myWayPoint.linkedTo.Remove(targetWayPoint);
|
||||
myWayPoint.OnLinksChanged?.Invoke(myWayPoint);
|
||||
targetWayPoint.FindHull();
|
||||
targetWayPoint.linkedTo.Remove(myWayPoint);
|
||||
targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1058,7 +1061,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.linkedTo.Any()) return;
|
||||
if (!item.linkedTo.Any()) { return; }
|
||||
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
|
||||
@@ -475,6 +475,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
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, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user