Unstable v0.1100.0.4 (November 11th 2020)
This commit is contained in:
@@ -91,9 +91,12 @@ namespace Barotrauma
|
||||
|
||||
private float avoidTimer;
|
||||
private float observeTimer;
|
||||
private float sweepTimer;
|
||||
|
||||
public bool StayInsideLevel = true;
|
||||
|
||||
private readonly IEnumerable<Body> myBodies;
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||
public PetBehavior PetBehavior { get; private set; }
|
||||
@@ -223,6 +226,7 @@ namespace Barotrauma
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f);
|
||||
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
|
||||
public CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
@@ -434,7 +438,7 @@ namespace Barotrauma
|
||||
UpdateIdle(deltaTime);
|
||||
break;
|
||||
case AIState.Attack:
|
||||
run = !IsCoolDownRunning;
|
||||
run = !IsCoolDownRunning || AttackingLimb != null && AttackingLimb.attack.FullSpeedAfterAttack;
|
||||
UpdateAttack(deltaTime);
|
||||
break;
|
||||
case AIState.Eat:
|
||||
@@ -471,7 +475,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (squaredDistance <= Math.Pow(reactDistance + movementMargin, 2))
|
||||
if (squaredDistance <= Math.Pow(reactDistance, 2))
|
||||
{
|
||||
float halfReactDistance = reactDistance / 2;
|
||||
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
|
||||
@@ -483,17 +487,12 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
|
||||
if (movementMargin <= 0)
|
||||
{
|
||||
movementMargin = halfReactDistance;
|
||||
}
|
||||
movementMargin = MathHelper.Clamp(movementMargin += deltaTime, halfReactDistance, reactDistance);
|
||||
UpdateEscape(deltaTime);
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * 0.5f * Rand.Range(0.75f, 1.25f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movementMargin = 0;
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -617,7 +616,7 @@ namespace Barotrauma
|
||||
|
||||
#region Idle
|
||||
|
||||
private void UpdateIdle(float deltaTime)
|
||||
private void UpdateIdle(float deltaTime, bool followLastTarget = true)
|
||||
{
|
||||
var pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
if (pathSteering == null)
|
||||
@@ -630,32 +629,35 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
if (followLastTarget)
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
if (memory != null)
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
{
|
||||
var location = memory.Location;
|
||||
float dist = Vector2.DistanceSquared(WorldPosition, location);
|
||||
if (dist < 50 * 50)
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
if (memory != null)
|
||||
{
|
||||
// Target is gone
|
||||
ResetAITarget();
|
||||
var location = memory.Location;
|
||||
float dist = Vector2.DistanceSquared(WorldPosition, location);
|
||||
if (dist < 50 * 50)
|
||||
{
|
||||
// Target is gone
|
||||
ResetAITarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Steer towards the target
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(target.Entity, location), 5);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Steer towards the target
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(target.Entity, location), 5);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
return;
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
if (pathSteering != null && !Character.AnimController.InWater)
|
||||
{
|
||||
@@ -686,7 +688,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else if (selectedTargetMemory != null)
|
||||
else if (selectedTargetMemory != null && SelectedAiTarget?.Entity is Character)
|
||||
{
|
||||
selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement;
|
||||
}
|
||||
@@ -880,7 +882,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth))
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 2);
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
var velocity = Vector2.Normalize(door.LinkedGap.FlowTargetHull.WorldPosition - Character.WorldPosition);
|
||||
steeringManager.SteeringManual(deltaTime, velocity);
|
||||
@@ -1040,6 +1042,48 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIBehaviorAfterAttack.IdleUntilCanAttack:
|
||||
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
AttackingLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cooldown not yet expired
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIBehaviorAfterAttack.FollowThrough:
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: true);
|
||||
return;
|
||||
@@ -1145,7 +1189,56 @@ namespace Barotrauma
|
||||
// Check that we can reach the target
|
||||
distance = toTarget.Length();
|
||||
canAttack = distance < AttackingLimb.attack.Range;
|
||||
if (!canAttack && !IsCoolDownRunning)
|
||||
if (canAttack)
|
||||
{
|
||||
if (AttackingLimb.attack.Ranged)
|
||||
{
|
||||
// Check that is facing the target
|
||||
float offset = AttackingLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
Vector2 forward = VectorExtensions.Forward(AttackingLimb.body.TransformedRotation - offset * Character.AnimController.Dir);
|
||||
float angle = VectorExtensions.Angle(forward, toTarget);
|
||||
canAttack = angle < MathHelper.ToRadians(AttackingLimb.attack.RequiredAngle);
|
||||
if (canAttack && AttackingLimb.attack.AvoidFriendlyFire)
|
||||
{
|
||||
float minDistance = MathUtils.Pow(ConvertUnits.ToDisplayUnits(Character.AnimController.Collider.GetMaxExtent() * 3), 2);
|
||||
bool IsFarEnough(Character other) => Vector2.DistanceSquared(Character.WorldPosition, other.WorldPosition) > minDistance;
|
||||
if (SwarmBehavior != null)
|
||||
{
|
||||
canAttack = SwarmBehavior.Members.All(c => c == Character || IsFarEnough(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
canAttack = Character.CharacterList.All(c => c == Character || !IsFriendly(Character, c) || IsFarEnough(c));
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
canAttack = !IsBlocked(attackSimPos) && !IsBlocked(AttackingLimb.SimPosition + forward * ConvertUnits.ToSimUnits(AttackingLimb.attack.Range));
|
||||
|
||||
bool IsBlocked(Vector2 targetPosition)
|
||||
{
|
||||
foreach (var body in Submarine.PickBodies(AttackingLimb.SimPosition, targetPosition, myBodies, Physics.CollisionCharacter))
|
||||
{
|
||||
Character hitTarget = null;
|
||||
if (body.UserData is Character c)
|
||||
{
|
||||
hitTarget = c;
|
||||
}
|
||||
else if (body.UserData is Limb limb)
|
||||
{
|
||||
hitTarget = limb.character;
|
||||
}
|
||||
if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsCoolDownRunning)
|
||||
{
|
||||
// If not, reset the attacking limb, if the cooldown is not running
|
||||
// Don't use the property, because we don't want cancel reversing, if we are reversing.
|
||||
@@ -1160,29 +1253,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
Limb steeringLimb = canAttack ? AttackingLimb : null;
|
||||
Limb steeringLimb = canAttack && !AttackingLimb.attack.Ranged ? AttackingLimb : null;
|
||||
if (steeringLimb == null)
|
||||
{
|
||||
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso.
|
||||
// If we always use the main limb, this causes the character to seek the target with it's torso/head, when it should not. For example Mudraptor steering with it's belly, when it should use it's head.
|
||||
// So let's use the one that's closer to the attacking limb.
|
||||
var torso = Character.AnimController.GetLimb(LimbType.Torso);
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (AttackingLimb == null)
|
||||
{
|
||||
steeringLimb = head ?? torso;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (head != null && torso != null)
|
||||
{
|
||||
steeringLimb = Vector2.DistanceSquared(AttackingLimb.SimPosition, head.SimPosition) < Vector2.DistanceSquared(AttackingLimb.SimPosition, torso.SimPosition) ? head : torso;
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringLimb = head ?? torso;
|
||||
}
|
||||
}
|
||||
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target.
|
||||
steeringLimb = Character.AnimController.GetLimb(LimbType.Head) ?? Character.AnimController.GetLimb(LimbType.Torso);
|
||||
}
|
||||
|
||||
if (steeringLimb == null)
|
||||
@@ -1190,7 +1265,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (AttackingLimb != null && AttackingLimb.attack.Retreat)
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, false);
|
||||
@@ -1250,6 +1325,25 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectedTargetingParams.SweepDistance > 0)
|
||||
{
|
||||
Vector2 toTarget = attackWorldPos - WorldPosition;
|
||||
if (distance <= 0)
|
||||
{
|
||||
distance = toTarget.Length();
|
||||
}
|
||||
float amplitude = MathHelper.Lerp(0, selectedTargetingParams.SweepStrength, MathUtils.InverseLerp(selectedTargetingParams.SweepDistance, 0, distance));
|
||||
if (amplitude > 0)
|
||||
{
|
||||
sweepTimer += deltaTime * selectedTargetingParams.SweepSpeed;
|
||||
float sin = (float)Math.Sin(sweepTimer) * amplitude;
|
||||
steerPos = MathUtils.RotatePointAroundTarget(attackSimPos, SimPosition, MathHelper.ToDegrees(sin));
|
||||
}
|
||||
else
|
||||
{
|
||||
sweepTimer = Rand.Range(-1000, 1000) * selectedTargetingParams.SweepSpeed;
|
||||
}
|
||||
}
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
@@ -1268,7 +1362,7 @@ namespace Barotrauma
|
||||
{
|
||||
IsSteeringThroughGap = true;
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 2);
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
Hull targetHull = section.gap?.FlowTargetHull;
|
||||
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
|
||||
@@ -1308,6 +1402,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (limb == ignoredLimb) { continue; }
|
||||
if (limb.IsSevered || limb.IsStuck) { continue; }
|
||||
if (limb.Disabled) { continue; }
|
||||
var attack = limb.attack;
|
||||
if (attack == null) { continue; }
|
||||
if (attack.CoolDownTimer > 0) { continue; }
|
||||
@@ -1318,6 +1413,17 @@ namespace Barotrauma
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
|
||||
}
|
||||
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { continue; }
|
||||
if (attack.Ranged)
|
||||
{
|
||||
// Check that is approximately facing the target
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : limb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
Vector2 forward = VectorExtensions.Forward(limb.body.TransformedRotation - offset * Character.AnimController.Dir);
|
||||
float angle = VectorExtensions.Angle(forward, toTarget);
|
||||
if (angle > MathHelper.ToRadians(attack.RequiredAngle)) { continue; }
|
||||
}
|
||||
|
||||
if (AIParams.RandomAttack)
|
||||
{
|
||||
attackLimbs.Add(limb);
|
||||
@@ -1407,7 +1513,7 @@ namespace Barotrauma
|
||||
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
|
||||
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
|
||||
}
|
||||
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
LatchOntoAI?.SetAttachTarget(wall, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner") || wall.Submarine != null && wall.Submarine == Character.Submarine)
|
||||
@@ -1450,13 +1556,13 @@ namespace Barotrauma
|
||||
|
||||
bool wasLatched = IsLatchedOnSub;
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 1);
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
|
||||
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
|
||||
avoidTimer = AIParams.AvoidTime * 0.5f * Rand.Range(0.75f, 1.25f);
|
||||
if (!isFriendly)
|
||||
{
|
||||
SelectTarget(attacker.AiTarget);
|
||||
@@ -1664,14 +1770,15 @@ namespace Barotrauma
|
||||
item.body.LinearVelocity *= 0.9f;
|
||||
item.body.LinearVelocity -= limbDiff * 0.25f;
|
||||
|
||||
bool wasBroken = item.Condition <= 0.0f;
|
||||
|
||||
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime);
|
||||
|
||||
if (item.Condition <= 0.0f)
|
||||
{
|
||||
if (!wasBroken) { PetBehavior?.OnEat(item.GetTags(), 1.0f); }
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
|
||||
PetBehavior?.OnEat(item.GetTags(), 0.1f / item.MaxCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2028,6 +2135,8 @@ namespace Barotrauma
|
||||
if (targetingTag == null) { continue; }
|
||||
var targetParams = GetTargetParams(targetingTag);
|
||||
if (targetParams == null) { continue; }
|
||||
if (targetParams.IgnoreWhileInside && character.CurrentHull != null) { continue; }
|
||||
if (targetParams.IgnoreWhileOutside && character.CurrentHull == null) { continue; }
|
||||
if (targetParams.State == AIState.Observe || targetParams.State == AIState.Eat)
|
||||
{
|
||||
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine)
|
||||
@@ -2036,10 +2145,7 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null) { continue; }
|
||||
valueModifier *= targetParams.Priority;
|
||||
|
||||
if (valueModifier == 0.0f) { continue; }
|
||||
@@ -2193,7 +2299,7 @@ namespace Barotrauma
|
||||
if (releaseTarget)
|
||||
{
|
||||
wallTarget = null;
|
||||
LatchOntoAI.DeattachFromBody(cooldown: 1);
|
||||
LatchOntoAI.DeattachFromBody(reset: true, cooldown: 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2407,7 +2513,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
LatchOntoAI?.DeattachFromBody(reset: true);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
escapeTarget = null;
|
||||
AttackingLimb = null;
|
||||
@@ -2448,14 +2554,15 @@ namespace Barotrauma
|
||||
foreach (var limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Disabled) { continue; }
|
||||
if (limb.attack == null) { continue; }
|
||||
if (!canAttackWalls)
|
||||
{
|
||||
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.StructureDamage > 0;
|
||||
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && (limb.attack.StructureDamage > 0 || limb.attack.Ranged);
|
||||
}
|
||||
if (!canAttackDoors)
|
||||
{
|
||||
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.ItemDamage > 0;
|
||||
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && (limb.attack.ItemDamage > 0 || limb.attack.Ranged);
|
||||
}
|
||||
if (!canAttackCharacters)
|
||||
{
|
||||
@@ -2502,7 +2609,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
public bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
{
|
||||
if (!wall.SectionBodyDisabled(sectionIndex)) return false;
|
||||
int holeCount = 1;
|
||||
@@ -2545,6 +2652,7 @@ namespace Barotrauma
|
||||
foreach (Limb limb in targetLimbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
float dist = Vector2.DistanceSquared(limb.WorldPosition, attackLimb.WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
|
||||
@@ -38,11 +38,49 @@ namespace Barotrauma
|
||||
private float respondToAttackTimer;
|
||||
private const float RespondToAttackInterval = 1.0f;
|
||||
|
||||
private bool freezeAI;
|
||||
|
||||
private readonly float maxSteeringBuffer = 5000;
|
||||
private readonly float minSteeringBuffer = 500;
|
||||
private readonly float steeringBufferIncreaseSpeed = 100;
|
||||
private float steeringBuffer;
|
||||
|
||||
private readonly float obstacleRaycastInterval = 1;
|
||||
private float obstacleRaycastTimer;
|
||||
|
||||
/// <summary>
|
||||
/// List of previous attacks done to this character
|
||||
/// </summary>
|
||||
private readonly Dictionary<Character, AttackResult> previousAttackResults = new Dictionary<Character, AttackResult>();
|
||||
|
||||
private readonly SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
|
||||
public override AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return objectiveManager; }
|
||||
}
|
||||
|
||||
public Order CurrentOrder
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string CurrentOrderOption
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float CurrentHullSafety { get; private set; } = 100;
|
||||
|
||||
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
|
||||
private readonly HashSet<Character> attackers = new HashSet<Character>();
|
||||
|
||||
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
|
||||
private class HullSafety
|
||||
{
|
||||
public float safety;
|
||||
@@ -72,35 +110,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
|
||||
public override AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return objectiveManager; }
|
||||
}
|
||||
|
||||
public Order CurrentOrder
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string CurrentOrderOption
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float CurrentHullSafety { get; private set; } = 100;
|
||||
|
||||
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
|
||||
private readonly HashSet<Character> attackers = new HashSet<Character>();
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
if (!c.IsHuman)
|
||||
@@ -117,8 +126,6 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
private bool freezeAI;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (DisableCrewAI || Character.Removed) { return; }
|
||||
@@ -176,15 +183,38 @@ namespace Barotrauma
|
||||
IgnoredItems.Clear();
|
||||
}
|
||||
|
||||
// Use the pathfinding also outside of the sub, but not farther than the extents of the sub + 500 units.
|
||||
if (Character.Submarine != null || SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null &&
|
||||
Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + 500, 2))
|
||||
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
|
||||
bool hasValidPath = steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable;
|
||||
|
||||
if (Character.Submarine == null && hasValidPath)
|
||||
{
|
||||
obstacleRaycastTimer -= deltaTime;
|
||||
if (obstacleRaycastTimer <= 0)
|
||||
{
|
||||
obstacleRaycastTimer = obstacleRaycastInterval;
|
||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||
{
|
||||
if (connectedSub == Submarine.MainSub) { continue; }
|
||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||
{
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Character.Submarine != null || hasValidPath && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer))
|
||||
{
|
||||
if (steeringManager != insideSteering)
|
||||
{
|
||||
insideSteering.Reset();
|
||||
}
|
||||
steeringManager = insideSteering;
|
||||
steeringBuffer += steeringBufferIncreaseSpeed * deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -193,7 +223,9 @@ namespace Barotrauma
|
||||
outsideSteering.Reset();
|
||||
}
|
||||
steeringManager = outsideSteering;
|
||||
steeringBuffer = minSteeringBuffer;
|
||||
}
|
||||
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
|
||||
|
||||
AnimController.Crouching = shouldCrouch;
|
||||
CheckCrouching(deltaTime);
|
||||
@@ -369,7 +401,7 @@ namespace Barotrauma
|
||||
if (!NeedsDivingGear(Character.CurrentHull, out bool needsSuit) || !needsSuit || oxygenLow)
|
||||
{
|
||||
bool shouldKeepTheGearOn = Character.AnimController.HeadInWater
|
||||
|| Character.Submarine.TeamID != Character.TeamID && Character.Submarine.TeamID != Character.TeamType.FriendlyNPC
|
||||
|| Character.Submarine.TeamID != Character.TeamID
|
||||
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|
||||
|| ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character // wait order
|
||||
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
|
||||
@@ -435,6 +467,7 @@ namespace Barotrauma
|
||||
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
divingSuit.Drop(Character);
|
||||
HandleRelocation(divingSuit);
|
||||
}
|
||||
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit)
|
||||
{
|
||||
@@ -461,6 +494,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
divingSuit.Drop(Character);
|
||||
HandleRelocation(divingSuit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,6 +512,7 @@ namespace Barotrauma
|
||||
if (ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
mask.Drop(Character);
|
||||
HandleRelocation(mask);
|
||||
}
|
||||
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
|
||||
{
|
||||
@@ -501,6 +536,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
mask.Drop(Character);
|
||||
HandleRelocation(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,6 +584,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
item.Drop(Character);
|
||||
HandleRelocation(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,6 +593,62 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<Item> itemsToRelocate = new HashSet<Item>();
|
||||
|
||||
private void HandleRelocation(Item item)
|
||||
{
|
||||
if (item.Submarine?.TeamID == Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
if (itemsToRelocate.Contains(item)) { return; }
|
||||
itemsToRelocate.Add(item);
|
||||
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
|
||||
{
|
||||
myPort.OnUnDocked += Relocate;
|
||||
}
|
||||
var campaign = GameMain.GameSession.Campaign;
|
||||
if (campaign != null)
|
||||
{
|
||||
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
|
||||
campaign.BeforeLevelLoading += Relocate;
|
||||
}
|
||||
}
|
||||
|
||||
void Relocate()
|
||||
{
|
||||
if (item == null || item.Removed) { return; }
|
||||
if (!itemsToRelocate.Contains(item)) { return; }
|
||||
var mainSub = Submarine.MainSub;
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.ParentInventory.Owner is Character c)
|
||||
{
|
||||
if (c.TeamID == Character.TeamType.Team1 || c.TeamID == Character.TeamType.Team2)
|
||||
{
|
||||
// Taken by a player/bot (if npc or monster would take the item, we'd probably still want it to spawn back to the main sub.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (item.ParentInventory.Owner.Submarine == mainSub)
|
||||
{
|
||||
// Placed inside an inventory that's already in the main sub.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Laying on ground inside the main sub.
|
||||
if (item.Submarine == mainSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub);
|
||||
if (wp != null)
|
||||
{
|
||||
item.Submarine = mainSub;
|
||||
item.SetTransform(wp.SimPosition, 0.0f);
|
||||
}
|
||||
itemsToRelocate.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReequipUnequipped()
|
||||
{
|
||||
foreach (var item in unequippedItems)
|
||||
@@ -585,6 +678,7 @@ namespace Barotrauma
|
||||
suitableContainer = null;
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredItems, customPriorityFunction: i =>
|
||||
{
|
||||
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (container.Inventory.IsFull()) { return 0; }
|
||||
@@ -1223,6 +1317,14 @@ namespace Barotrauma
|
||||
}
|
||||
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
|
||||
if (!otherCharacter.CanSeeCharacter(thief)) { continue; }
|
||||
// Don't react if the player is taking an extinguisher and there's any fires on the sub, or diving gear when the sub is flooding
|
||||
// -> allow them to use the emergency items
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
var connectedHulls = character.Submarine.GetHulls(alsoFromConnectedSubs: true);
|
||||
if (item.HasTag("fireextinguisher") && connectedHulls.Any(h => h.FireSources.Any())) { continue; }
|
||||
if (item.HasTag("diving") && connectedHulls.Any(h => h.ConnectedGaps.Any(g => AIObjectiveFixLeaks.IsValidTarget(g, thief)))) { continue; }
|
||||
}
|
||||
if (!someoneSpoke && !character.IsIncapacitated && character.Stun <= 0.0f)
|
||||
{
|
||||
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
@@ -1236,8 +1338,6 @@ namespace Barotrauma
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
|
||||
someoneSpoke = true;
|
||||
}
|
||||
// Don't react if the player is taking an extinguisher and there's any fires on the sub -> allow them to use the emergency items
|
||||
if (item.HasTag("fireextinguisher") && character.Submarine.GetHulls(alsoFromConnectedSubs: true).Any(h => h.FireSources.Any())) { continue; }
|
||||
// React if we are security
|
||||
if (!TriggerSecurity(otherHumanAI))
|
||||
{
|
||||
@@ -1464,7 +1564,16 @@ namespace Barotrauma
|
||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
|
||||
}
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
|
||||
float dangerousItemsFactor = 1f;
|
||||
foreach (Item item in Item.ItemList.Where(it => it.CurrentHull == hull))
|
||||
{
|
||||
if (item.Prefab != null && item.Prefab.IsDangerous)
|
||||
{
|
||||
dangerousItemsFactor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor;
|
||||
return MathHelper.Clamp(safety * 100, 0, 100);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,13 +192,43 @@ namespace Barotrauma
|
||||
pathFinder.InsideSubmarine = character.Submarine != null;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1;
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0;
|
||||
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
{
|
||||
// It's possible that the current path was calculated from a start point that is no longer valid.
|
||||
// Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node.
|
||||
useNewPath = newPath.Cost < currentPath.Cost ||
|
||||
Vector2.DistanceSquared(character.WorldPosition, currentPath.CurrentNode.WorldPosition) > Math.Pow(Vector2.Distance(character.WorldPosition, newPath.Nodes.First().WorldPosition) * 3, 2);
|
||||
// Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset).
|
||||
if (IsIdenticalPath())
|
||||
{
|
||||
useNewPath = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the new path if it has significantly lower cost (don't change the path if it has marginally smaller cost. This reduces navigating backwards due to new path that is calculated from the node just behind us).
|
||||
float t = (float)currentPath.CurrentIndex / (currentPath.Nodes.Count - 1);
|
||||
useNewPath = newPath.Cost < currentPath.Cost * MathHelper.Lerp(0.95f, 0, t);
|
||||
if (!useNewPath)
|
||||
{
|
||||
// It's possible that the current path was calculated from a start point that is no longer valid.
|
||||
// Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node.
|
||||
useNewPath = Vector2.DistanceSquared(character.WorldPosition, currentPath.CurrentNode.WorldPosition) > Math.Pow(Vector2.Distance(character.WorldPosition, newPath.Nodes.First().WorldPosition) * 3, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsIdenticalPath()
|
||||
{
|
||||
int nodeCount = newPath.Nodes.Count;
|
||||
if (nodeCount == currentPath.Nodes.Count)
|
||||
{
|
||||
for (int i = 0; i < nodeCount - 1; i++)
|
||||
{
|
||||
if (newPath.Nodes[i] != currentPath.Nodes[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (useNewPath)
|
||||
{
|
||||
@@ -407,7 +437,7 @@ namespace Barotrauma
|
||||
if (door.IsOpen) { return true; }
|
||||
if (door.Item.NonInteractable) { return false; }
|
||||
if (CanBreakDoors) { return true; }
|
||||
if (door.IsStuck) { return false; }
|
||||
if (door.IsStuck || door.IsJammed) { return false; }
|
||||
if (!canOpenDoors || character.LockHands) { return false; }
|
||||
if (door.HasIntegratedButtons)
|
||||
{
|
||||
@@ -689,7 +719,10 @@ namespace Barotrauma
|
||||
if (wander)
|
||||
{
|
||||
SteeringWander();
|
||||
SteeringAvoid(deltaTime, lookAheadDistance: ConvertUnits.ToSimUnits(wallAvoidDistance), 5);
|
||||
if (inWater)
|
||||
{
|
||||
SteeringAvoid(deltaTime, lookAheadDistance: ConvertUnits.ToSimUnits(wallAvoidDistance), 5);
|
||||
}
|
||||
}
|
||||
if (!inWater)
|
||||
{
|
||||
|
||||
@@ -15,15 +15,16 @@ namespace Barotrauma
|
||||
|
||||
private float raycastTimer;
|
||||
|
||||
private Body attachTargetBody;
|
||||
private Structure targetWall;
|
||||
private Body targetBody;
|
||||
private Vector2 attachSurfaceNormal;
|
||||
private Submarine attachTargetSubmarine;
|
||||
private Submarine targetSubmarine;
|
||||
|
||||
public bool AttachToSub { get; private set; }
|
||||
public bool AttachToWalls { get; private set; }
|
||||
|
||||
private float minDeattachSpeed = 3.0f, maxDeattachSpeed = 10.0f;
|
||||
private float damageOnDetach = 0.0f, detachStun = 0.0f;
|
||||
private readonly float minDeattachSpeed, maxDeattachSpeed;
|
||||
private readonly float damageOnDetach, detachStun;
|
||||
private float deattachTimer;
|
||||
|
||||
private Vector2 wallAttachPos;
|
||||
@@ -35,13 +36,8 @@ namespace Barotrauma
|
||||
private float attachLimbRotation;
|
||||
|
||||
private float jointDir;
|
||||
|
||||
private List<WeldJoint> attachJoints = new List<WeldJoint>();
|
||||
|
||||
public List<WeldJoint> AttachJoints
|
||||
{
|
||||
get { return attachJoints; }
|
||||
}
|
||||
public List<WeldJoint> AttachJoints { get; } = new List<WeldJoint>();
|
||||
|
||||
public Vector2? WallAttachPos
|
||||
{
|
||||
@@ -49,19 +45,16 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsAttached
|
||||
{
|
||||
get { return attachJoints.Count > 0; }
|
||||
}
|
||||
public bool IsAttached => AttachJoints.Count > 0;
|
||||
|
||||
public bool IsAttachedToSub => IsAttached && (attachTargetBody?.UserData is Submarine || attachTargetBody?.UserData is Entity entity && entity.Submarine != null);
|
||||
public bool IsAttachedToSub => IsAttached && targetSubmarine != null;
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
AttachToWalls = element.GetAttributeBool("attachtowalls", false);
|
||||
AttachToSub = element.GetAttributeBool("attachtosub", false);
|
||||
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 3.0f);
|
||||
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 10.0f));
|
||||
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 5.0f);
|
||||
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 8.0f));
|
||||
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
|
||||
detachStun = element.GetAttributeFloat("detachstun", 0.0f);
|
||||
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
|
||||
@@ -84,10 +77,11 @@ namespace Barotrauma
|
||||
enemyAI.Character.OnDeath += OnCharacterDeath;
|
||||
}
|
||||
|
||||
public void SetAttachTarget(Body attachTarget, Submarine attachTargetSub, Vector2 attachPos, Vector2 attachSurfaceNormal)
|
||||
public void SetAttachTarget(Structure wall, Vector2 attachPos, Vector2 attachSurfaceNormal)
|
||||
{
|
||||
attachTargetBody = attachTarget;
|
||||
attachTargetSubmarine = attachTargetSub;
|
||||
targetWall = wall;
|
||||
targetBody = wall.Submarine.PhysicsBody.FarseerBody;
|
||||
targetSubmarine = wall.Submarine;
|
||||
this.attachSurfaceNormal = attachSurfaceNormal;
|
||||
wallAttachPos = attachPos;
|
||||
}
|
||||
@@ -98,28 +92,27 @@ namespace Barotrauma
|
||||
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
DeattachFromBody();
|
||||
WallAttachPos = null;
|
||||
DeattachFromBody(reset: true);
|
||||
return;
|
||||
}
|
||||
if (attachJoints.Count > 0)
|
||||
if (AttachJoints.Count > 0)
|
||||
{
|
||||
if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir))
|
||||
{
|
||||
attachJoints[0].LocalAnchorA =
|
||||
new Vector2(-attachJoints[0].LocalAnchorA.X, attachJoints[0].LocalAnchorA.Y);
|
||||
attachJoints[0].ReferenceAngle = -attachJoints[0].ReferenceAngle;
|
||||
AttachJoints[0].LocalAnchorA =
|
||||
new Vector2(-AttachJoints[0].LocalAnchorA.X, AttachJoints[0].LocalAnchorA.Y);
|
||||
AttachJoints[0].ReferenceAngle = -AttachJoints[0].ReferenceAngle;
|
||||
jointDir = attachLimb.Dir;
|
||||
}
|
||||
for (int i = 0; i < attachJoints.Count; i++)
|
||||
for (int i = 0; i < AttachJoints.Count; i++)
|
||||
{
|
||||
//something went wrong, limb body is very far from the joint anchor -> deattach
|
||||
if (Vector2.DistanceSquared(attachJoints[i].WorldAnchorB, attachJoints[i].BodyA.Position) > 10.0f * 10.0f)
|
||||
if (Vector2.DistanceSquared(AttachJoints[i].WorldAnchorB, AttachJoints[i].BodyA.Position) > 10.0f * 10.0f)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
|
||||
#endif
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -135,9 +128,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 transformedAttachPos = wallAttachPos;
|
||||
if (character.Submarine == null && attachTargetSubmarine != null)
|
||||
if (character.Submarine == null && targetSubmarine != null)
|
||||
{
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(attachTargetSubmarine.Position);
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(targetSubmarine.Position);
|
||||
}
|
||||
if (transformedAttachPos != Vector2.Zero)
|
||||
{
|
||||
@@ -168,7 +161,7 @@ namespace Barotrauma
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
attachSurfaceNormal = edge.GetNormal(cell);
|
||||
attachTargetBody = cell.Body;
|
||||
targetBody = cell.Body;
|
||||
Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection);
|
||||
float distSqr = Vector2.DistanceSquared(character.SimPosition, wallAttachPos);
|
||||
if (distSqr < closestDist)
|
||||
@@ -192,7 +185,7 @@ namespace Barotrauma
|
||||
|
||||
if (wallAttachPos == Vector2.Zero)
|
||||
{
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -201,13 +194,13 @@ namespace Barotrauma
|
||||
if (squaredDistance < targetDistance * targetDistance)
|
||||
{
|
||||
//close enough to a wall -> attach
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, wallAttachPos);
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, targetBody, wallAttachPos);
|
||||
enemyAI.SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
//move closer to the wall
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: false);
|
||||
enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f);
|
||||
enemyAI.SteeringManager.SteeringSeek(wallAttachPos);
|
||||
}
|
||||
@@ -217,44 +210,60 @@ namespace Barotrauma
|
||||
case AIState.Aggressive:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && targetBody != null)
|
||||
{
|
||||
// is not attached or is attached to something else
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB != attachTargetBody)
|
||||
if (!IsAttached || IsAttached && AttachJoints[0].BodyB != targetBody)
|
||||
{
|
||||
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
||||
{
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos);
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, targetBody, transformedAttachPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WallAttachPos = null;
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsAttached && attachTargetBody != null && deattachTimer < 0.0f)
|
||||
if (IsAttached && targetBody != null && targetWall != null && targetSubmarine != null && deattachTimer <= 0.0f)
|
||||
{
|
||||
Entity entity = attachTargetBody.UserData as Entity;
|
||||
Submarine attachedSub = entity is Submarine sub ? sub : entity?.Submarine;
|
||||
if (attachedSub != null)
|
||||
bool deattach = false;
|
||||
// Deattach if the wall is broken enough where we are attached to
|
||||
int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true);
|
||||
if (enemyAI.CanPassThroughHole(targetWall, targetSection))
|
||||
{
|
||||
float velocity = attachedSub.Velocity == Vector2.Zero ? 0.0f : attachedSub.Velocity.Length();
|
||||
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
|
||||
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
|
||||
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
|
||||
deattach = true;
|
||||
attachCooldown = 2;
|
||||
}
|
||||
if (!deattach)
|
||||
{
|
||||
// Deattach if the velocity is high
|
||||
float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length();
|
||||
deattach = velocity > maxDeattachSpeed;
|
||||
if (!deattach)
|
||||
{
|
||||
DeattachFromBody();
|
||||
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
|
||||
attachCooldown = 5.0f;
|
||||
if (velocity > minDeattachSpeed)
|
||||
{
|
||||
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
|
||||
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
|
||||
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
|
||||
{
|
||||
deattach = true;
|
||||
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
|
||||
attachCooldown = detachStun * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deattach)
|
||||
{
|
||||
DeattachFromBody(reset: true);
|
||||
}
|
||||
deattachTimer = 5.0f;
|
||||
}
|
||||
}
|
||||
@@ -263,11 +272,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (attachCooldown > 0) { return; }
|
||||
//already attached to something
|
||||
if (attachJoints.Count > 0)
|
||||
if (AttachJoints.Count > 0)
|
||||
{
|
||||
//already attached to the target body, no need to do anything
|
||||
if (attachJoints[0].BodyB == targetBody) { return; }
|
||||
DeattachFromBody();
|
||||
if (AttachJoints[0].BodyB == targetBody) { return; }
|
||||
DeattachFromBody(reset: false);
|
||||
}
|
||||
|
||||
jointDir = attachLimb.Dir;
|
||||
@@ -290,7 +299,7 @@ namespace Barotrauma
|
||||
CollideConnected = false,
|
||||
};
|
||||
GameMain.World.Add(limbJoint);
|
||||
attachJoints.Add(limbJoint);
|
||||
AttachJoints.Add(limbJoint);
|
||||
|
||||
// Limb scale is already taken into account when creating the collider.
|
||||
Vector2 colliderFront = collider.GetLocalFront();
|
||||
@@ -309,25 +318,37 @@ namespace Barotrauma
|
||||
//Length = 0.1f
|
||||
};
|
||||
GameMain.World.Add(colliderJoint);
|
||||
attachJoints.Add(colliderJoint);
|
||||
AttachJoints.Add(colliderJoint);
|
||||
}
|
||||
|
||||
public void DeattachFromBody(float cooldown = 0)
|
||||
public void DeattachFromBody(bool reset, float cooldown = 0)
|
||||
{
|
||||
foreach (Joint joint in attachJoints)
|
||||
foreach (Joint joint in AttachJoints)
|
||||
{
|
||||
GameMain.World.Remove(joint);
|
||||
}
|
||||
attachJoints.Clear();
|
||||
AttachJoints.Clear();
|
||||
if (cooldown > 0)
|
||||
{
|
||||
attachCooldown = cooldown;
|
||||
}
|
||||
if (reset)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
targetWall = null;
|
||||
targetSubmarine = null;
|
||||
targetBody = null;
|
||||
WallAttachPos = null;
|
||||
}
|
||||
|
||||
private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
DeattachFromBody();
|
||||
DeattachFromBody(reset: true);
|
||||
character.OnDeath -= OnCharacterDeath;
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
|
||||
+25
-5
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -58,12 +59,10 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.ParentInventory != null) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
//var rootContainer = item.GetRootContainer();
|
||||
//// Only target items lying on the ground (= not inside a container) (do we need this check?)
|
||||
//if (rootContainer != null) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
if (pickable is Holdable h && h.Attachable && h.Attached) { return false; }
|
||||
@@ -80,7 +79,28 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return item.Prefab.PreferredContainers.Any();
|
||||
if (item.Prefab.PreferredContainers.None())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool canEquip = true;
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
canEquip = false;
|
||||
foreach (var allowedSlot in item.AllowedSlots)
|
||||
{
|
||||
int slot = character.Inventory.FindLimbSlot(allowedSlot);
|
||||
if (slot > -1)
|
||||
{
|
||||
if (character.Inventory.Items[slot] == null)
|
||||
{
|
||||
canEquip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return canEquip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -59,7 +59,7 @@ namespace Barotrauma
|
||||
protected override bool Check()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (container == null)
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI()))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
@@ -86,7 +86,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (container == null)
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI()))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
@@ -94,6 +94,11 @@ namespace Barotrauma
|
||||
Item itemToContain = item ?? character.Inventory.FindItem(i => CheckItem(i) && i.Container != container.Item, recursive: true);
|
||||
if (itemToContain != null)
|
||||
{
|
||||
if (!character.CanInteractWith(itemToContain))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (character.CanInteractWith(container.Item, out _, checkLinked: false))
|
||||
{
|
||||
if (RemoveEmpty)
|
||||
@@ -142,11 +147,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: should we just use GetItem?
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name
|
||||
TargetName = container.Item.Name,
|
||||
abortCondition = () => !itemToContain.IsOwnedBy(character)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+7
-1
@@ -79,11 +79,17 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
return new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
|
||||
var getItemObjective = new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
|
||||
{
|
||||
AllowStealing = true,
|
||||
// If the item is inside an unsafe hull, decrease the priority
|
||||
GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
|
||||
};
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>())
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindfireextinguisher"), null, 0.0f, "dialogcannotfindfireextinguisher", 10.0f);
|
||||
};
|
||||
return getItemObjective;
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Hull hull, Character character)
|
||||
{
|
||||
if (hull == null) { return false; }
|
||||
if (hull.IgnoreByAI) { return false; }
|
||||
if (hull.FireSources.None()) { return false; }
|
||||
if (hull.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ namespace Barotrauma
|
||||
}
|
||||
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
|
||||
{
|
||||
AllowStealing = true,
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true
|
||||
};
|
||||
|
||||
+8
-1
@@ -65,7 +65,14 @@ namespace Barotrauma
|
||||
if (weldingTool == null)
|
||||
{
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC),
|
||||
onAbandon: () => Abandon = true,
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcannotfindweldingequipment"), null, 0.0f, "dialogcannotfindweldingequipment", 10.0f);
|
||||
}
|
||||
Abandon = true;
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref getWeldingTool));
|
||||
return;
|
||||
}
|
||||
|
||||
+2
@@ -71,6 +71,8 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Gap gap, Character character)
|
||||
{
|
||||
if (gap == null) { return false; }
|
||||
// Don't fix a leak on a wall section set to be ignored
|
||||
if (gap.ConnectedWall?.Sections?.Any(s => s.gap == gap && s.IgnoreByAI) ?? false) { return false; }
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null || character.Submarine == null) { return false; }
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
|
||||
+14
-13
@@ -18,13 +18,13 @@ namespace Barotrauma
|
||||
public float TargetCondition { get; set; } = 1;
|
||||
public bool AllowDangerousPressure { get; set; }
|
||||
|
||||
private string[] identifiersOrTags;
|
||||
private readonly string[] identifiersOrTags;
|
||||
|
||||
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
|
||||
private bool spawnItemIfNotFound = false;
|
||||
|
||||
private Item targetItem;
|
||||
private Item originalTarget;
|
||||
private readonly Item originalTarget;
|
||||
private ISpatialEntity moveToTarget;
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
@@ -32,13 +32,18 @@ namespace Barotrauma
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private bool checkInventory;
|
||||
private readonly bool checkInventory;
|
||||
|
||||
public static float DefaultReach = 100;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool MustBeSpecificItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the character allowed to take the item from somewhere else than their own sub (e.g. an outpost)
|
||||
/// </summary>
|
||||
public bool AllowStealing { get; set; }
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
@@ -246,9 +251,13 @@ namespace Barotrauma
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||
Submarine mySub = character.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
Submarine mySub = character.Submarine;
|
||||
if (mySub == null) { continue; }
|
||||
if (!AllowStealing)
|
||||
{
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
|
||||
}
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
@@ -339,6 +348,7 @@ namespace Barotrauma
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsThisOrAnyContainerIgnoredByAI()) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
@@ -362,14 +372,5 @@ namespace Barotrauma
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (objectiveManager.CurrentOrder != null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCannotFindItem"), null, 0.0f, "cannotfinditem", 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-7
@@ -30,6 +30,8 @@ namespace Barotrauma
|
||||
public bool followControlledCharacter;
|
||||
public bool mimic;
|
||||
|
||||
public float extraDistanceWhileSwimming;
|
||||
public float extraDistanceOutsideSub;
|
||||
private float _closeEnough = 50;
|
||||
private readonly float minDistance = 50;
|
||||
/// <summary>
|
||||
@@ -37,7 +39,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public float CloseEnough
|
||||
{
|
||||
get { return _closeEnough; }
|
||||
get
|
||||
{
|
||||
float dist = _closeEnough;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
dist += extraDistanceWhileSwimming;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
dist += extraDistanceOutsideSub;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
set
|
||||
{
|
||||
_closeEnough = Math.Max(minDistance, value);
|
||||
@@ -324,13 +338,15 @@ namespace Barotrauma
|
||||
Func<PathNode, bool> nodeFilter = null;
|
||||
if (isInside && !AllowGoingOutside)
|
||||
{
|
||||
nodeFilter = node => node.Waypoint.CurrentHull != null;
|
||||
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
||||
}
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(Target), 1, n =>
|
||||
{
|
||||
if (n.Waypoint.isObstructed) { return false; }
|
||||
return (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null);
|
||||
}, endNodeFilter, nodeFilter, CheckVisibility);
|
||||
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(Target), 1,
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
|
||||
endNodeFilter,
|
||||
nodeFilter,
|
||||
CheckVisibility);
|
||||
|
||||
if (!isInside && PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
if (useScooter)
|
||||
|
||||
+22
-9
@@ -21,14 +21,13 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
behavior = value;
|
||||
if (behavior == BehaviorType.StayInHull && character.TeamID != Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
DebugConsole.NewMessage($"AIObjectiveIdle.BehaviorType.StayInHull is implemented only for outpost NPCs. Using passive behavior for {character.Name} ({character.Info.Job.Prefab.Identifier})", color: Color.Red);
|
||||
behavior = BehaviorType.Passive;
|
||||
}
|
||||
switch (behavior)
|
||||
{
|
||||
case BehaviorType.Active:
|
||||
newTargetIntervalMin = 10;
|
||||
newTargetIntervalMax = 20;
|
||||
standStillMin = 2;
|
||||
standStillMax = 10;
|
||||
break;
|
||||
case BehaviorType.Passive:
|
||||
case BehaviorType.StayInHull:
|
||||
newTargetIntervalMin = 60;
|
||||
@@ -36,6 +35,18 @@ namespace Barotrauma
|
||||
standStillMin = 30;
|
||||
standStillMax = 60;
|
||||
break;
|
||||
case BehaviorType.Active:
|
||||
newTargetIntervalMin = 40;
|
||||
newTargetIntervalMax = 60;
|
||||
standStillMin = 20;
|
||||
standStillMax = 40;
|
||||
break;
|
||||
case BehaviorType.Patrol:
|
||||
newTargetIntervalMin = 15;
|
||||
newTargetIntervalMax = 30;
|
||||
standStillMin = 5;
|
||||
standStillMax = 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,9 +60,10 @@ namespace Barotrauma
|
||||
|
||||
public enum BehaviorType
|
||||
{
|
||||
Active,
|
||||
Patrol,
|
||||
Passive,
|
||||
StayInHull
|
||||
StayInHull,
|
||||
Active
|
||||
}
|
||||
public Hull TargetHull { get; set; }
|
||||
private Hull currentTarget;
|
||||
@@ -434,10 +446,11 @@ namespace Barotrauma
|
||||
targetHulls.Add(hull);
|
||||
float weight = hull.RectWidth;
|
||||
// Prefer rooms that are closer. Avoid rooms that are not in the same level.
|
||||
// If the behavior is active, prefer rooms that are not close.
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 2500, dist));
|
||||
float distanceFactor = behavior == BehaviorType.Patrol ? MathHelper.Lerp(1, 0, MathUtils.InverseLerp(2500, 0, dist)) : MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 2500, dist));
|
||||
float waterFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 100, hull.WaterPercentage * 2));
|
||||
weight *= distanceFactor * waterFactor;
|
||||
hullWeights.Add(weight);
|
||||
|
||||
+6
-1
@@ -245,7 +245,10 @@ namespace Barotrauma
|
||||
public void SortObjectives()
|
||||
{
|
||||
CurrentOrder?.GetPriority();
|
||||
Objectives.ForEach(o => o.GetPriority());
|
||||
for (int i = Objectives.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Objectives[i].GetPriority();
|
||||
}
|
||||
if (Objectives.Any())
|
||||
{
|
||||
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
@@ -305,6 +308,8 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
CloseEnough = Rand.Range(90, 100) + Rand.Range(50, 70) * Math.Min(HumanAIController.CountCrew(c => c.ObjectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.Target == orderGiver, onlyBots: true), 4),
|
||||
extraDistanceOutsideSub = 100,
|
||||
extraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
followControlledCharacter = orderGiver == character,
|
||||
|
||||
+13
-4
@@ -157,11 +157,20 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
|
||||
if (objectiveManager.CurrentOrder != this && HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
// If this is not an order...
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
{
|
||||
// Don't abandon
|
||||
return;
|
||||
// Don't allow to operate an item that someone with a better skills already operates
|
||||
if (HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
{
|
||||
// Don't abandon
|
||||
return;
|
||||
}
|
||||
if (component.Item.IgnoreByAI || (useController && controller.Item.IgnoreByAI))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (operateTarget != null)
|
||||
{
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.IgnoreByAI) { return false; }
|
||||
if (pump.Item.NonInteractable) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
|
||||
+6
-1
@@ -90,7 +90,12 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
subObjectives.Add(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true));
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true);
|
||||
if (objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>())
|
||||
{
|
||||
getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair"), null, 0.0f, "dialogcannotfindrequireditemtorepair", 10.0f);
|
||||
}
|
||||
subObjectives.Add(getItemObjective);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
+1
@@ -148,6 +148,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
|
||||
@@ -94,7 +94,9 @@ namespace Barotrauma
|
||||
|
||||
|
||||
//if true, the order is issued to all available characters
|
||||
public bool TargetAllCharacters;
|
||||
public bool TargetAllCharacters { get; }
|
||||
public bool IsReport => TargetAllCharacters && !MustSetTarget;
|
||||
|
||||
|
||||
public readonly float FadeOutTime;
|
||||
|
||||
@@ -132,11 +134,31 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (targetSpatialEntity == null) { targetSpatialEntity = TargetEntity ?? TargetPosition as ISpatialEntity; }
|
||||
if (targetSpatialEntity == null)
|
||||
{
|
||||
if (TargetType == OrderTargetType.WallSection && WallSectionIndex.HasValue)
|
||||
{
|
||||
targetSpatialEntity = (TargetEntity as Structure)?.Sections[WallSectionIndex.Value];
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSpatialEntity = TargetEntity ?? TargetPosition as ISpatialEntity;
|
||||
}
|
||||
}
|
||||
return targetSpatialEntity;
|
||||
}
|
||||
}
|
||||
|
||||
public enum OrderTargetType
|
||||
{
|
||||
Entity,
|
||||
Position,
|
||||
WallSection
|
||||
}
|
||||
public OrderTargetType TargetType { get; }
|
||||
public int? WallSectionIndex { get; }
|
||||
public bool IsIgnoreOrder { get; }
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
Prefabs = new Dictionary<string, Order>();
|
||||
@@ -292,6 +314,7 @@ namespace Barotrauma
|
||||
|
||||
IsPrefab = true;
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
IsIgnoreOrder = Identifier == "ignorethis" || Identifier == "unignorethis";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -299,7 +322,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Prefab = prefab.Prefab ?? prefab;
|
||||
|
||||
Name = prefab.Name;
|
||||
Identifier = prefab.Identifier;
|
||||
@@ -317,6 +340,7 @@ namespace Barotrauma
|
||||
AppropriateSkill = prefab.AppropriateSkill;
|
||||
Category = prefab.Category;
|
||||
MustManuallyAssign = prefab.MustManuallyAssign;
|
||||
IsIgnoreOrder = prefab.IsIgnoreOrder;
|
||||
|
||||
OrderGiver = orderGiver;
|
||||
TargetEntity = targetEntity;
|
||||
@@ -337,12 +361,21 @@ namespace Barotrauma
|
||||
TargetItemComponent = targetItem;
|
||||
}
|
||||
|
||||
TargetType = OrderTargetType.Entity;
|
||||
|
||||
IsPrefab = false;
|
||||
}
|
||||
|
||||
public Order(Order prefab, OrderTarget target, Character orderGiver = null) : this(prefab, targetEntity: null, targetItem: null, orderGiver)
|
||||
{
|
||||
TargetPosition = target;
|
||||
TargetType = OrderTargetType.Position;
|
||||
}
|
||||
|
||||
public Order(Order prefab, Structure wall, int? sectionIndex, Character orderGiver = null) : this(prefab, targetEntity: wall, null, orderGiver: orderGiver)
|
||||
{
|
||||
WallSectionIndex = sectionIndex;
|
||||
TargetType = OrderTargetType.WallSection;
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
|
||||
@@ -7,41 +7,32 @@ namespace Barotrauma
|
||||
{
|
||||
class PathNode
|
||||
{
|
||||
private readonly int wayPointID;
|
||||
|
||||
public int state;
|
||||
|
||||
public PathNode Parent;
|
||||
|
||||
private Vector2 position;
|
||||
|
||||
public float F, G, H;
|
||||
|
||||
public List<PathNode> connections;
|
||||
public readonly List<PathNode> connections = new List<PathNode>();
|
||||
public List<float> distances;
|
||||
|
||||
public Vector2 TempPosition;
|
||||
public float TempDistance;
|
||||
|
||||
public WayPoint Waypoint { get; private set; }
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return position; }
|
||||
}
|
||||
public readonly WayPoint Waypoint;
|
||||
public readonly Vector2 Position;
|
||||
public readonly int WayPointID;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"PathNode {wayPointID}";
|
||||
return $"PathNode {WayPointID}";
|
||||
}
|
||||
|
||||
public PathNode(WayPoint wayPoint)
|
||||
{
|
||||
this.Waypoint = wayPoint;
|
||||
this.position = wayPoint.SimPosition;
|
||||
wayPointID = wayPoint.ID;
|
||||
|
||||
connections = new List<PathNode>();
|
||||
Waypoint = wayPoint;
|
||||
Position = wayPoint.SimPosition;
|
||||
WayPointID = Waypoint.ID;
|
||||
}
|
||||
|
||||
public static List<PathNode> GenerateNodes(List<WayPoint> wayPoints)
|
||||
@@ -78,7 +69,7 @@ namespace Barotrauma
|
||||
node.distances = new List<float>();
|
||||
for (int i = 0; i < node.connections.Count; i++)
|
||||
{
|
||||
node.distances.Add(Vector2.Distance(node.position, node.connections[i].position));
|
||||
node.distances.Add(Vector2.Distance(node.Position, node.connections[i].Position));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +83,7 @@ namespace Barotrauma
|
||||
public GetNodePenaltyHandler GetNodePenalty;
|
||||
|
||||
private readonly List<PathNode> nodes;
|
||||
public readonly bool IndoorsSteering;
|
||||
|
||||
public bool InsideSubmarine { get; set; }
|
||||
public bool ApplyPenaltyToOutsideNodes { get; set; }
|
||||
@@ -105,7 +97,7 @@ namespace Barotrauma
|
||||
wp.linkedTo.CollectionChanged += WaypointLinksChanged;
|
||||
}
|
||||
|
||||
InsideSubmarine = indoorsSteering;
|
||||
IndoorsSteering = indoorsSteering;
|
||||
}
|
||||
|
||||
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
@@ -201,11 +193,13 @@ namespace Barotrauma
|
||||
if (nodeFilter != null && !nodeFilter(node)) { continue; }
|
||||
if (startNodeFilter != null && !startNodeFilter(node)) { continue; }
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (InsideSubmarine)
|
||||
if (IndoorsSteering)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { continue; }
|
||||
|
||||
// Always check the visibility for the start node
|
||||
var body = Submarine.PickBody(
|
||||
start, node.TempPosition, null,
|
||||
start, node.TempPosition, null,
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
{
|
||||
@@ -259,17 +253,20 @@ namespace Barotrauma
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(node)) { continue; }
|
||||
if (endNodeFilter != null && !endNodeFilter(node)) { continue; }
|
||||
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (InsideSubmarine && checkVisibility)
|
||||
if (IndoorsSteering)
|
||||
{
|
||||
// Only check the visibility for the end node when allowed (fix leaks)
|
||||
var body = Submarine.PickBody(end, node.TempPosition, null,
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs );
|
||||
if (body != null)
|
||||
if (node.Waypoint.isObstructed) { continue; }
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (checkVisibility)
|
||||
{
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
// Only check the visibility for the end node when allowed (fix leaks)
|
||||
var body = Submarine.PickBody(end, node.TempPosition, null,
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
{
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
}
|
||||
endNode = node;
|
||||
@@ -343,6 +340,7 @@ namespace Barotrauma
|
||||
foreach (PathNode node in nodes)
|
||||
{
|
||||
if (node.state != 1) { continue; }
|
||||
if (IndoorsSteering && node.Waypoint.isObstructed) { continue; }
|
||||
if (filter != null && !filter(node)) { continue; }
|
||||
if (node.F < dist)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -18,8 +19,19 @@ namespace Barotrauma
|
||||
Hungry
|
||||
}
|
||||
|
||||
public float Hunger { get; set; } = 50.0f;
|
||||
public float Happiness { get; set; } = 50.0f;
|
||||
private float hunger = 50.0f;
|
||||
public float Hunger
|
||||
{
|
||||
get { return hunger; }
|
||||
set { hunger = MathHelper.Clamp(value, 0.0f, MaxHunger); }
|
||||
}
|
||||
|
||||
private float happiness = 50.0f;
|
||||
public float Happiness
|
||||
{
|
||||
get { return happiness; }
|
||||
set { happiness = MathHelper.Clamp(value, 0.0f, MaxHappiness); }
|
||||
}
|
||||
|
||||
public float MaxHappiness { get; set; }
|
||||
public float MaxHunger { get; set; }
|
||||
@@ -86,9 +98,10 @@ namespace Barotrauma
|
||||
switch (subElement.Name.LocalName.ToLowerInvariant())
|
||||
{
|
||||
case "item":
|
||||
string identifier = subElement.GetAttributeString("identifier", "");
|
||||
Item newItemToProduce = new Item
|
||||
{
|
||||
Prefab = ItemPrefab.Find("", subElement.GetAttributeString("identifier", "")),
|
||||
Prefab = string.IsNullOrEmpty(identifier) ? null : ItemPrefab.Find("", subElement.GetAttributeString("identifier", "")),
|
||||
Commonness = subElement.GetAttributeFloat("commonness", 0.0f)
|
||||
};
|
||||
totalCommonness += newItemToProduce.Commonness;
|
||||
@@ -119,7 +132,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
aggregate += Items[i].Commonness;
|
||||
if (aggregate >= r)
|
||||
if (aggregate >= r && Items[i].Prefab != null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition);
|
||||
break;
|
||||
@@ -192,25 +205,21 @@ namespace Barotrauma
|
||||
public StatusIndicatorType GetCurrentStatusIndicatorType()
|
||||
{
|
||||
if (Hunger > MaxHunger * 0.5f) { return StatusIndicatorType.Hungry; }
|
||||
if (Happiness > MaxHappiness * 0.75f) { return StatusIndicatorType.Happy; }
|
||||
if (Happiness > MaxHappiness * 0.8f) { return StatusIndicatorType.Happy; }
|
||||
if (Happiness < MaxHappiness * 0.25f) { return StatusIndicatorType.Sad; }
|
||||
return StatusIndicatorType.None;
|
||||
}
|
||||
|
||||
public void OnEat(IEnumerable<string> tags, float amount)
|
||||
public bool OnEat(IEnumerable<string> tags, float amount)
|
||||
{
|
||||
for (int i = 0; i < foods.Count; i++)
|
||||
foreach (string tag in tags)
|
||||
{
|
||||
if (tags.Any(t => t.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
Hunger += foods[i].Hunger * amount;
|
||||
Happiness += foods[i].Happiness * amount;
|
||||
break;
|
||||
}
|
||||
if (OnEat(tag, amount)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnEat(string tag, float amount)
|
||||
public bool OnEat(string tag, float amount)
|
||||
{
|
||||
for (int i = 0; i < foods.Count; i++)
|
||||
{
|
||||
@@ -218,9 +227,13 @@ namespace Barotrauma
|
||||
{
|
||||
Hunger += foods[i].Hunger * amount;
|
||||
Happiness += foods[i].Happiness * amount;
|
||||
break;
|
||||
#if CLIENT
|
||||
AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.5f);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Play(Character player)
|
||||
@@ -230,9 +243,11 @@ namespace Barotrauma
|
||||
PlayTimer = 5.0f;
|
||||
AiController.Character.IsRagdolled = true;
|
||||
Happiness += 10.0f;
|
||||
if (Happiness > MaxHappiness) { Happiness = MaxHappiness; }
|
||||
AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce);
|
||||
unstunY = AiController.Character.SimPosition.Y;
|
||||
#if CLIENT
|
||||
AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.9f);
|
||||
#endif
|
||||
}
|
||||
|
||||
public string GetTagName()
|
||||
@@ -259,15 +274,6 @@ namespace Barotrauma
|
||||
{
|
||||
var character = AiController.Character;
|
||||
if (character?.Removed ?? true || character.IsDead) { return; }
|
||||
if (GameMain.NetworkMember?.IsClient ?? false) { return; }
|
||||
|
||||
if (Owner != null && (Owner.Removed || Owner.IsDead)) { Owner = null; }
|
||||
|
||||
Hunger += HungerIncreaseRate * deltaTime;
|
||||
|
||||
Happiness -= HappinessDecreaseRate * deltaTime;
|
||||
|
||||
PlayTimer -= deltaTime;
|
||||
|
||||
if (unstunY.HasValue)
|
||||
{
|
||||
@@ -292,6 +298,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
PlayTimer -= deltaTime;
|
||||
|
||||
if (GameMain.NetworkMember?.IsClient ?? false) { return; }
|
||||
if (Owner != null && (Owner.Removed || Owner.IsDead)) { Owner = null; }
|
||||
|
||||
Hunger += HungerIncreaseRate * deltaTime;
|
||||
Happiness -= HappinessDecreaseRate * deltaTime;
|
||||
|
||||
for (int i = 0; i < foods.Count; i++)
|
||||
{
|
||||
Food food = foods[i];
|
||||
@@ -311,12 +325,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (Hunger < 0.0f) { Hunger = 0.0f; }
|
||||
if (Hunger > MaxHunger) { Hunger = MaxHunger; }
|
||||
if (Happiness < 0.0f) { Happiness = 0.0f; }
|
||||
if (Happiness > MaxHappiness) { Happiness = MaxHappiness; }
|
||||
if (PlayTimer < 0.0f) { PlayTimer = 0.0f; }
|
||||
|
||||
if (Hunger >= MaxHunger * 0.99f)
|
||||
{
|
||||
character.CharacterHealth.ApplyAffliction(character.AnimController.MainLimb, new Affliction(AfflictionPrefab.InternalDamage, 8.0f * deltaTime));
|
||||
@@ -343,7 +351,7 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.IsPet || c.IsDead) { continue; }
|
||||
if (c.Submarine?.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (c.Submarine == null) { continue; }
|
||||
|
||||
var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior;
|
||||
if (petBehavior == null) { continue; }
|
||||
@@ -396,14 +404,32 @@ namespace Barotrauma
|
||||
if (petBehavior != null)
|
||||
{
|
||||
petBehavior.Owner = owner;
|
||||
var petBehaviorElement = subElement.Attribute("petbehavior");
|
||||
var petBehaviorElement = subElement.Element("petbehavior");
|
||||
if (petBehaviorElement != null)
|
||||
{
|
||||
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat(50.0f);
|
||||
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat(50.0f);
|
||||
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat("hunger", 50.0f);
|
||||
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat("happiness", 50.0f);
|
||||
}
|
||||
}
|
||||
|
||||
var inventoryElement = subElement.Element("inventory");
|
||||
if (inventoryElement != null)
|
||||
{
|
||||
pet.SpawnInventoryItems(pet.Inventory, inventoryElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.WriteRangedSingle(Happiness, 0.0f, MaxHappiness, 8);
|
||||
msg.WriteRangedSingle(Hunger, 0.0f, MaxHunger, 8);
|
||||
}
|
||||
|
||||
public void ClientRead(IReadMessage msg)
|
||||
{
|
||||
Happiness = msg.ReadRangedSingle(0.0f, MaxHappiness, 8);
|
||||
Hunger = msg.ReadRangedSingle(0.0f, MaxHunger, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace Barotrauma
|
||||
{
|
||||
get { return aiController; }
|
||||
}
|
||||
|
||||
|
||||
public AICharacter(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isNetworkPlayer = false, RagdollParams ragdoll = null)
|
||||
: base(speciesName, position, seed, characterInfo, isNetworkPlayer, ragdoll)
|
||||
: base(speciesName, position, seed, characterInfo, id: Entity.NullEntityID, isRemotePlayer: isNetworkPlayer, ragdollParams: ragdoll)
|
||||
{
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
+35
-10
@@ -407,8 +407,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (CurrentSwimParams == null) { return; }
|
||||
movement = TargetMovement;
|
||||
|
||||
if (movement.LengthSquared() > 0.00001f)
|
||||
bool isMoving = movement.LengthSquared() > 0.00001f;
|
||||
if (isMoving)
|
||||
{
|
||||
float t = 0.5f;
|
||||
if (CurrentSwimParams.RotateTowardsMovement && VectorExtensions.Angle(VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2), movement) > MathHelper.PiOver2)
|
||||
@@ -425,7 +425,7 @@ namespace Barotrauma
|
||||
mainLimb.PullJointEnabled = true;
|
||||
//mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
|
||||
if (movement.LengthSquared() < 0.00001f)
|
||||
if (!isMoving)
|
||||
{
|
||||
WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5);
|
||||
mainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
@@ -625,7 +625,8 @@ namespace Barotrauma
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true);
|
||||
float movementFactor = Math.Max(character.AnimController.Collider.LinearVelocity.Length() * 0.5f, 1);
|
||||
limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque * movementFactor, wrapAngle: true);
|
||||
}
|
||||
if (limb.Params.BlinkFrequency > 0)
|
||||
{
|
||||
@@ -703,7 +704,7 @@ namespace Barotrauma
|
||||
if (head != null)
|
||||
{
|
||||
bool headFacingBackwards = false;
|
||||
if (HeadAngle.HasValue)
|
||||
if (HeadAngle.HasValue && head != mainLimb)
|
||||
{
|
||||
SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque);
|
||||
if (Math.Sign(head.SimPosition.X - mainLimb.SimPosition.X) != Math.Sign(Dir))
|
||||
@@ -853,11 +854,35 @@ namespace Barotrauma
|
||||
float noise = (PerlinNoise.GetPerlin(WalkPos * 0.002f, WalkPos * 0.003f) - 0.5f) * 5.0f;
|
||||
float animStrength = (1.0f - deathAnimTimer / deathAnimDuration);
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head != null && head.IsSevered) { return; }
|
||||
Limb baseLimb = GetLimb(LimbType.Head);
|
||||
//if head is the main limb, it technically can't be severed - the rest of the limbs are considered severed if the head gets cut off
|
||||
if (baseLimb == MainLimb)
|
||||
{
|
||||
int connectedToHeadCount = GetConnectedLimbs(baseLimb).Count;
|
||||
//if there's nothing connected to the head, don't make it wiggle by itself
|
||||
if (connectedToHeadCount == 1) { baseLimb = null; }
|
||||
Limb torso = GetLimb(LimbType.Torso, excludeSevered: false);
|
||||
if (torso != null)
|
||||
{
|
||||
//if there are more limbs connected to the torso than to the head, make the torso wiggle instead
|
||||
int connectedToTorsoCount = GetConnectedLimbs(torso).Count;
|
||||
if (connectedToTorsoCount > connectedToHeadCount)
|
||||
{
|
||||
baseLimb = torso;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (baseLimb == null)
|
||||
{
|
||||
baseLimb = GetLimb(LimbType.Torso, excludeSevered: true);
|
||||
if (baseLimb == null) { return; }
|
||||
}
|
||||
|
||||
var connectedToBaseLimb = GetConnectedLimbs(baseLimb);
|
||||
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
if (head != null && !head.IsSevered) head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength);
|
||||
if (tail != null && !tail.IsSevered) tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength);
|
||||
if (baseLimb != null) { baseLimb.body.ApplyTorque((float)(Math.Sqrt(baseLimb.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); }
|
||||
if (tail != null && connectedToBaseLimb.Contains(tail)) { tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); }
|
||||
|
||||
WalkPos += deltaTime * 10.0f * animStrength;
|
||||
|
||||
@@ -865,7 +890,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (!connectedToBaseLimb.Contains(limb)) { continue; }
|
||||
#if CLIENT
|
||||
if (limb.LightSource != null)
|
||||
{
|
||||
|
||||
+14
-9
@@ -1974,9 +1974,6 @@ namespace Barotrauma
|
||||
|
||||
public override void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
var rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
useItemTimer = 0.5f;
|
||||
Anim = Animation.UsingConstruction;
|
||||
|
||||
@@ -1999,13 +1996,21 @@ namespace Barotrauma
|
||||
handSimPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
|
||||
leftHand.Disabled = true;
|
||||
leftHand.PullJointEnabled = true;
|
||||
leftHand.PullJointWorldAnchorB = handSimPos;
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
if (leftHand != null)
|
||||
{
|
||||
leftHand.Disabled = true;
|
||||
leftHand.PullJointEnabled = true;
|
||||
leftHand.PullJointWorldAnchorB = handSimPos;
|
||||
}
|
||||
|
||||
rightHand.Disabled = true;
|
||||
rightHand.PullJointEnabled = true;
|
||||
rightHand.PullJointWorldAnchorB = handSimPos;
|
||||
var rightHand = GetLimb(LimbType.RightHand);
|
||||
if (rightHand != null)
|
||||
{
|
||||
rightHand.Disabled = true;
|
||||
rightHand.PullJointEnabled = true;
|
||||
rightHand.PullJointWorldAnchorB = handSimPos;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flip()
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.ignoreCollisions;
|
||||
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.IgnoreCollisions && !limb.Hidden;
|
||||
return mainLimb;
|
||||
}
|
||||
}
|
||||
@@ -782,6 +782,14 @@ namespace Barotrauma
|
||||
|
||||
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound);
|
||||
|
||||
protected List<Limb> GetConnectedLimbs(Limb limb)
|
||||
{
|
||||
connectedLimbs.Clear();
|
||||
checkedJoints.Clear();
|
||||
GetConnectedLimbs(connectedLimbs, checkedJoints, limb);
|
||||
return connectedLimbs;
|
||||
}
|
||||
|
||||
private void GetConnectedLimbs(List<Limb> connectedLimbs, List<LimbJoint> checkedJoints, Limb limb)
|
||||
{
|
||||
connectedLimbs.Add(limb);
|
||||
@@ -1052,7 +1060,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.ignoreCollisions || limb.IsSevered) { continue; }
|
||||
if (limb.IgnoreCollisions || limb.IsSevered) { continue; }
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1626,7 +1634,8 @@ namespace Barotrauma
|
||||
|
||||
protected void CheckDistFromCollider()
|
||||
{
|
||||
float allowedDist = Math.Max(Math.Max(Collider.radius, Collider.width), Collider.height) * 2.0f;
|
||||
float allowedDist = Math.Max(Math.Max(Collider.radius, Collider.width), Collider.height) * 2.0f;
|
||||
allowedDist = Math.Max(allowedDist, 1.0f);
|
||||
float resetDist = allowedDist * 5.0f;
|
||||
|
||||
Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace Barotrauma
|
||||
PursueIfCanAttack,
|
||||
Pursue,
|
||||
FollowThrough,
|
||||
FollowThroughUntilCanAttack
|
||||
FollowThroughUntilCanAttack,
|
||||
IdleUntilCanAttack
|
||||
}
|
||||
|
||||
struct AttackResult
|
||||
@@ -117,12 +118,24 @@ namespace Barotrauma
|
||||
[Serialize(0f, true, description: "A random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases)."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)]
|
||||
public float CoolDownRandomFactor { get; private set; } = 0;
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool FullSpeedAfterAttack { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
|
||||
public float StructureDamage { get; set; }
|
||||
|
||||
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float ItemDamage { get; set; }
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool Ranged { get; set; }
|
||||
|
||||
[Serialize(false, true, description:"Only affects ranged attacks.")]
|
||||
public bool AvoidFriendlyFire { get; set; }
|
||||
|
||||
[Serialize(20f, true)]
|
||||
public float RequiredAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Legacy support. Use Afflictions.
|
||||
/// </summary>
|
||||
@@ -379,7 +392,7 @@ namespace Barotrauma
|
||||
ReloadAfflictions(element);
|
||||
}
|
||||
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
|
||||
{
|
||||
Character targetCharacter = target as Character;
|
||||
if (OnlyHumans)
|
||||
@@ -403,6 +416,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.sourceBody = sourceBody;
|
||||
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
@@ -423,14 +437,18 @@ namespace Barotrauma
|
||||
effect.Apply(effectType, deltaTime, targetCharacter, targetCharacter.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
}
|
||||
if (target is Entity entity)
|
||||
if (target is Entity targetEntity)
|
||||
{
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, deltaTime, entity, targets);
|
||||
effect.Apply(effectType, deltaTime, targetEntity, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetEntity, attacker, worldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,7 +456,7 @@ namespace Barotrauma
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
|
||||
{
|
||||
if (targetLimb == null)
|
||||
{
|
||||
@@ -462,6 +480,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.sourceBody = sourceBody;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
@@ -478,6 +497,17 @@ namespace Barotrauma
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, attacker, worldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
return attackResult;
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool IsRemotePlayer { get; set; }
|
||||
|
||||
public bool IsLocalPlayer => Controlled == this;
|
||||
public bool IsPlayer => Controlled == this || IsRemotePlayer;
|
||||
public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled;
|
||||
|
||||
@@ -314,6 +315,7 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
hideFaceTimer = MathHelper.Clamp(hideFaceTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
|
||||
if (info != null && info.IsDisguisedAsAnother != HideFace) info.CheckDisguiseStatus(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,9 +746,9 @@ namespace Barotrauma
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null)
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null)
|
||||
{
|
||||
return Create(characterInfo.SpeciesName, position, seed, characterInfo, isRemotePlayer, hasAi, true, ragdoll);
|
||||
return Create(characterInfo.SpeciesName, position, seed, characterInfo, id, isRemotePlayer, hasAi, true, ragdoll);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -756,16 +758,24 @@ namespace Barotrauma
|
||||
/// <param name="position">Position in display units.</param>
|
||||
/// <param name="seed">RNG seed to use if the character config has randomizable parameters.</param>
|
||||
/// <param name="characterInfo">The name, gender, etc of the character. Only used for humans, and if the parameter is not given, a random CharacterInfo is generated.</param>
|
||||
/// <param name="id">ID to assign to the character. If set to 0, automatically find an available ID.</param>
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="createNetworkEvent">Should clients receive a network event about the creation of this character?</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null)
|
||||
public static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null)
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
speciesName = Path.GetFileNameWithoutExtension(speciesName).ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (CharacterPrefab.FindBySpeciesName(speciesName) == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to create character \"{speciesName}\". Matching prefab not found.\n" + Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
Character newCharacter = null;
|
||||
if (!speciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -783,7 +793,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
newCharacter = new Character(speciesName, position, seed, characterInfo, isRemotePlayer, ragdoll);
|
||||
newCharacter = new Character(speciesName, position, seed, characterInfo, id: id, isRemotePlayer: isRemotePlayer, ragdollParams: ragdoll);
|
||||
}
|
||||
|
||||
float healthRegen = newCharacter.Params.Health.ConstantHealthRegeneration;
|
||||
@@ -823,8 +833,8 @@ namespace Barotrauma
|
||||
return newCharacter;
|
||||
}
|
||||
|
||||
protected Character(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
|
||||
: base(null)
|
||||
protected Character(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
|
||||
: base(null, id)
|
||||
{
|
||||
prefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
|
||||
@@ -1461,7 +1471,7 @@ namespace Barotrauma
|
||||
AnimController.ReleaseStuckLimbs();
|
||||
if (AIController != null && AIController is EnemyAIController enemyAI)
|
||||
{
|
||||
enemyAI.LatchOntoAI?.DeattachFromBody();
|
||||
enemyAI.LatchOntoAI?.DeattachFromBody(reset: true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1517,6 +1527,7 @@ namespace Barotrauma
|
||||
var validLimbs = AnimController.Limbs.Where(l =>
|
||||
{
|
||||
if (l.IsSevered || l.IsStuck) { return false; }
|
||||
if (l.Disabled) { return false; }
|
||||
var attack = l.attack;
|
||||
if (attack == null) { return false; }
|
||||
if (attack.CoolDownTimer > 0) { return false; }
|
||||
@@ -1629,6 +1640,7 @@ namespace Barotrauma
|
||||
foreach (Limb limb in target.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || limb == target.AnimController.MainLimb) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
Vector2 limbDir = limb.WorldPosition - WorldPosition;
|
||||
float leftDot = Vector2.Dot(limbDir, leftDir);
|
||||
if (leftDot > leftMostDot)
|
||||
@@ -2919,7 +2931,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
// Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head)
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: attacker.IsHuman || attacker.IsPlayer);
|
||||
TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: attacker == null || attacker.IsHuman || attacker.IsPlayer);
|
||||
|
||||
return attackResult;
|
||||
}
|
||||
@@ -2962,7 +2974,7 @@ namespace Barotrauma
|
||||
if (severed)
|
||||
{
|
||||
Limb otherLimb = joint.LimbA == targetLimb ? joint.LimbB : joint.LimbA;
|
||||
otherLimb.body.ApplyLinearImpulse(targetLimb.LinearVelocity * targetLimb.Mass);
|
||||
otherLimb.body.ApplyLinearImpulse(targetLimb.LinearVelocity * targetLimb.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
|
||||
ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
targetLimb.ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
otherLimb.ApplyStatusEffects(ActionType.OnSevered, 1.0f);
|
||||
@@ -3423,6 +3435,75 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SpawnInventoryItems(Inventory inventory, XElement itemData)
|
||||
{
|
||||
SpawnInventoryItemsRecursive(inventory, itemData);
|
||||
}
|
||||
|
||||
private void SpawnInventoryItemsRecursive(Inventory inventory, XElement element)
|
||||
{
|
||||
foreach (XElement itemElement in element.Elements())
|
||||
{
|
||||
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true, idRemap: IdRemap.DiscardId);
|
||||
if (newItem == null) { continue; }
|
||||
|
||||
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
|
||||
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
#if SERVER
|
||||
newItem.GetComponent<Terminal>()?.SyncHistory();
|
||||
#endif
|
||||
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
|
||||
if (!slotIndices.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure there's no other item in the slot
|
||||
//this should not happen normally, but can occur if the character is accidentally given new job items while also loading previous items in the campaign
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i) && inventory.Items[i] != null && inventory.Items[i] != newItem)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading character inventory data. The slot {i} was already occupied by the item \"{inventory.Items[i].Name} ({inventory.Items[i].ID})\" when loading the item \"{newItem.Name} ({newItem.ID})\"");
|
||||
inventory.Items[i].Drop(null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
|
||||
newItem.ParentInventory = inventory;
|
||||
|
||||
//force the item to the correct slots
|
||||
// e.g. putting the item in a hand slot will also put it in the first available Any-slot,
|
||||
// which may not be where it actually was
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i))
|
||||
{
|
||||
inventory.Items[i] = newItem;
|
||||
}
|
||||
else if (inventory.Items[i] == newItem)
|
||||
{
|
||||
inventory.Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int itemContainerIndex = 0;
|
||||
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
|
||||
foreach (XElement childInvElement in itemElement.Elements())
|
||||
{
|
||||
if (itemContainerIndex >= itemContainers.Count) break;
|
||||
if (!childInvElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement);
|
||||
itemContainerIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly HashSet<AttackContext> currentContexts = new HashSet<AttackContext>();
|
||||
|
||||
public IEnumerable<AttackContext> GetAttackContexts()
|
||||
|
||||
@@ -151,19 +151,21 @@ namespace Barotrauma
|
||||
public XElement HealthData;
|
||||
|
||||
private static ushort idCounter;
|
||||
private const string disguiseName = "???";
|
||||
|
||||
public string Name;
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
string disguiseName = "?";
|
||||
if (Character == null || !Character.HideFace)
|
||||
{
|
||||
IsDisguised = IsDisguisedAsAnother = false;
|
||||
return Name;
|
||||
}
|
||||
else if ((GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowDisguises))
|
||||
{
|
||||
IsDisguised = IsDisguisedAsAnother = false;
|
||||
return Name;
|
||||
}
|
||||
|
||||
@@ -263,6 +265,62 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDisguised = false;
|
||||
public bool IsDisguisedAsAnother = false;
|
||||
|
||||
public void CheckDisguiseStatus(bool handleBuff, IdCard idCard = null)
|
||||
{
|
||||
if (Character == null) { return; }
|
||||
|
||||
string currentlyDisplayedName = DisplayName;
|
||||
|
||||
IsDisguised = currentlyDisplayedName == disguiseName;
|
||||
IsDisguisedAsAnother = !IsDisguised && currentlyDisplayedName != Name;
|
||||
|
||||
if (IsDisguisedAsAnother)
|
||||
{
|
||||
if (handleBuff)
|
||||
{
|
||||
Character.CharacterHealth.ApplyAffliction(Character.AnimController.GetLimb(LimbType.Head), AfflictionPrefab.List.FirstOrDefault(a => a.Identifier.Equals("disguised", StringComparison.OrdinalIgnoreCase)).Instantiate(100f));
|
||||
}
|
||||
|
||||
if (idCard != null)
|
||||
{
|
||||
#if CLIENT
|
||||
GetDisguisedSprites(idCard);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (Character.Inventory != null)
|
||||
{
|
||||
int cardSlotIndex = Character.Inventory.FindLimbSlot(InvSlotType.Card);
|
||||
if (cardSlotIndex >= 0)
|
||||
{
|
||||
idCard = Character.Inventory.Items[cardSlotIndex].GetComponent<IdCard>();
|
||||
|
||||
if (idCard != null)
|
||||
{
|
||||
#if CLIENT
|
||||
GetDisguisedSprites(idCard);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
disguisedJobIcon = null;
|
||||
disguisedPortrait = null;
|
||||
#endif
|
||||
|
||||
if (handleBuff)
|
||||
{
|
||||
Character.CharacterHealth.ReduceAffliction(Character.AnimController.GetLimb(LimbType.Head), "disguised", 100f);
|
||||
}
|
||||
}
|
||||
|
||||
private List<WearableSprite> attachmentSprites;
|
||||
public List<WearableSprite> AttachmentSprites
|
||||
{
|
||||
@@ -582,7 +640,7 @@ namespace Barotrauma
|
||||
return id;
|
||||
}
|
||||
|
||||
public IEnumerable<XElement> FilterByTypeAndHeadID(IEnumerable<XElement> elements, WearableType targetType)
|
||||
public IEnumerable<XElement> FilterByTypeAndHeadID(IEnumerable<XElement> elements, WearableType targetType, int headSpriteId)
|
||||
{
|
||||
if (elements == null) { return elements; }
|
||||
return elements.Where(e =>
|
||||
@@ -590,16 +648,16 @@ namespace Barotrauma
|
||||
if (Enum.TryParse(e.GetAttributeString("type", ""), true, out WearableType type) && type != targetType) { return false; }
|
||||
int headId = e.GetAttributeInt("headid", -1);
|
||||
// if the head id is less than 1, the id is not valid and the condition is ignored.
|
||||
return headId < 1 || headId == Head.HeadSpriteId;
|
||||
return headId < 1 || headId == headSpriteId;
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<XElement> FilterElementsByGenderAndRace(IEnumerable<XElement> elements)
|
||||
public IEnumerable<XElement> FilterElementsByGenderAndRace(IEnumerable<XElement> elements, Gender gender, Race race)
|
||||
{
|
||||
if (elements == null) { return elements; }
|
||||
return elements.Where(w =>
|
||||
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == Head.gender &&
|
||||
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == Head.race);
|
||||
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == gender &&
|
||||
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == race);
|
||||
}
|
||||
|
||||
private void LoadHeadPresets()
|
||||
@@ -639,7 +697,7 @@ namespace Barotrauma
|
||||
{
|
||||
var wearableElements = Wearables;
|
||||
if (wearableElements == null) { return; }
|
||||
var wearables = FilterElementsByGenderAndRace(wearableElements).ToList();
|
||||
var wearables = FilterElementsByGenderAndRace(wearableElements, head.gender, head.race).ToList();
|
||||
if (wearables == null)
|
||||
{
|
||||
Head.headSpriteRange = Vector2.Zero;
|
||||
@@ -739,19 +797,19 @@ namespace Barotrauma
|
||||
if (hairs == null)
|
||||
{
|
||||
float commonness = Gender == Gender.Female ? 0.05f : 0.2f;
|
||||
hairs = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.Hair), WearableType.Hair, commonness);
|
||||
hairs = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.Hair, head.HeadSpriteId), WearableType.Hair, commonness);
|
||||
}
|
||||
if (beards == null)
|
||||
{
|
||||
beards = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.Beard), WearableType.Beard);
|
||||
beards = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.Beard, head.HeadSpriteId), WearableType.Beard);
|
||||
}
|
||||
if (moustaches == null)
|
||||
{
|
||||
moustaches = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.Moustache), WearableType.Moustache);
|
||||
moustaches = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.Moustache, head.HeadSpriteId), WearableType.Moustache);
|
||||
}
|
||||
if (faceAttachments == null)
|
||||
{
|
||||
faceAttachments = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables), WearableType.FaceAttachment), WearableType.FaceAttachment);
|
||||
faceAttachments = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, head.gender, head.race), WearableType.FaceAttachment, head.HeadSpriteId), WearableType.FaceAttachment);
|
||||
}
|
||||
|
||||
if (IsValidIndex(Head.HairIndex, hairs))
|
||||
@@ -790,49 +848,49 @@ namespace Barotrauma
|
||||
Head.FaceAttachment = GetRandomElement(faceAttachments);
|
||||
Head.FaceAttachmentIndex = faceAttachments.IndexOf(Head.FaceAttachment);
|
||||
}
|
||||
|
||||
static List<XElement> AddEmpty(IEnumerable<XElement> elements, WearableType type, float commonness = 1)
|
||||
{
|
||||
// Let's add an empty element so that there's a chance that we don't get any actual element -> allows bald and beardless guys, for example.
|
||||
var emptyElement = new XElement("EmptyWearable", type.ToString(), new XAttribute("commonness", commonness));
|
||||
var list = new List<XElement>() { emptyElement };
|
||||
list.AddRange(elements);
|
||||
return list;
|
||||
}
|
||||
|
||||
XElement GetRandomElement(IEnumerable<XElement> elements)
|
||||
{
|
||||
var filtered = elements.Where(e => IsWearableAllowed(e));
|
||||
if (filtered.Count() == 0) { return null; }
|
||||
var element = ToolBox.SelectWeightedRandom(filtered.ToList(), GetWeights(filtered).ToList(), Rand.RandSync.Unsynced);
|
||||
return element == null || element.Name == "Empty" ? null : element;
|
||||
}
|
||||
|
||||
bool IsWearableAllowed(XElement element)
|
||||
{
|
||||
string spriteName = element.Element("sprite").GetAttributeString("name", string.Empty);
|
||||
return IsAllowed(Head.HairElement, spriteName) && IsAllowed(Head.BeardElement, spriteName) && IsAllowed(Head.MoustacheElement, spriteName) && IsAllowed(Head.FaceAttachment, spriteName);
|
||||
}
|
||||
|
||||
bool IsAllowed(XElement element, string spriteName)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
var disallowed = element.GetAttributeStringArray("disallow", new string[0]);
|
||||
if (disallowed.Any(s => spriteName.Contains(s)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidIndex(int index, List<XElement> list) => index >= 0 && index < list.Count;
|
||||
|
||||
static IEnumerable<float> GetWeights(IEnumerable<XElement> elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<XElement> AddEmpty(IEnumerable<XElement> elements, WearableType type, float commonness = 1)
|
||||
{
|
||||
// Let's add an empty element so that there's a chance that we don't get any actual element -> allows bald and beardless guys, for example.
|
||||
var emptyElement = new XElement("EmptyWearable", type.ToString(), new XAttribute("commonness", commonness));
|
||||
var list = new List<XElement>() { emptyElement };
|
||||
list.AddRange(elements);
|
||||
return list;
|
||||
}
|
||||
|
||||
private XElement GetRandomElement(IEnumerable<XElement> elements)
|
||||
{
|
||||
var filtered = elements.Where(e => IsWearableAllowed(e));
|
||||
if (filtered.Count() == 0) { return null; }
|
||||
var element = ToolBox.SelectWeightedRandom(filtered.ToList(), GetWeights(filtered).ToList(), Rand.RandSync.Unsynced);
|
||||
return element == null || element.Name == "Empty" ? null : element;
|
||||
}
|
||||
|
||||
private bool IsWearableAllowed(XElement element)
|
||||
{
|
||||
string spriteName = element.Element("sprite").GetAttributeString("name", string.Empty);
|
||||
return IsAllowed(Head.HairElement, spriteName) && IsAllowed(Head.BeardElement, spriteName) && IsAllowed(Head.MoustacheElement, spriteName) && IsAllowed(Head.FaceAttachment, spriteName);
|
||||
}
|
||||
|
||||
private bool IsAllowed(XElement element, string spriteName)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
var disallowed = element.GetAttributeStringArray("disallow", new string[0]);
|
||||
if (disallowed.Any(s => spriteName.Contains(s)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidIndex(int index, List<XElement> list) => index >= 0 && index < list.Count;
|
||||
|
||||
private static IEnumerable<float> GetWeights(IEnumerable<XElement> elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f));
|
||||
|
||||
partial void LoadAttachmentSprites(bool omitJob);
|
||||
|
||||
private int CalculateSalary()
|
||||
@@ -925,72 +983,6 @@ namespace Barotrauma
|
||||
return charElement;
|
||||
}
|
||||
|
||||
public void SpawnInventoryItems(Inventory inventory, XElement itemData)
|
||||
{
|
||||
SpawnInventoryItemsRecursive(inventory, itemData);
|
||||
}
|
||||
|
||||
private void SpawnInventoryItemsRecursive(Inventory inventory, XElement element)
|
||||
{
|
||||
foreach (XElement itemElement in element.Elements())
|
||||
{
|
||||
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true);
|
||||
if (newItem == null) { continue; }
|
||||
|
||||
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
|
||||
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
|
||||
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
|
||||
if (!slotIndices.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure there's no other item in the slot
|
||||
//this should not happen normally, but can occur if the character is accidentally given new job items while also loading previous items in the campaign
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i) && inventory.Items[i] != null && inventory.Items[i] != newItem)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading character inventory data. The slot {i} was already occupied by the item \"{inventory.Items[i].Name} ({inventory.Items[i].ID})\" when loading the item \"{newItem.Name} ({newItem.ID})\"");
|
||||
inventory.Items[i].Drop(null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
|
||||
newItem.ParentInventory = inventory;
|
||||
|
||||
//force the item to the correct slots
|
||||
// e.g. putting the item in a hand slot will also put it in the first available Any-slot,
|
||||
// which may not be where it actually was
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i))
|
||||
{
|
||||
inventory.Items[i] = newItem;
|
||||
}
|
||||
else if (inventory.Items[i] == newItem)
|
||||
{
|
||||
inventory.Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int itemContainerIndex = 0;
|
||||
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
|
||||
foreach (XElement childInvElement in itemElement.Elements())
|
||||
{
|
||||
if (itemContainerIndex >= itemContainers.Count) break;
|
||||
if (!childInvElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement);
|
||||
itemContainerIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyHealthData(Character character, XElement healthData)
|
||||
{
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData); }
|
||||
|
||||
+12
-2
@@ -98,10 +98,11 @@ namespace Barotrauma
|
||||
|
||||
private void ApplyDamage(float deltaTime, bool applyForce)
|
||||
{
|
||||
int limbCount = character.AnimController.Limbs.Count(l => !l.ignoreCollisions && !l.IsSevered);
|
||||
int limbCount = character.AnimController.Limbs.Count(l => !l.IgnoreCollisions && !l.IsSevered);
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
float random = Rand.Value();
|
||||
huskInfection.Clear();
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / limbCount));
|
||||
@@ -170,7 +171,16 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);
|
||||
|
||||
XElement parentElement = new XElement("CharacterInfo");
|
||||
XElement infoElement = character.Info?.Save(parentElement);
|
||||
CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement);
|
||||
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer: false, hasAi: true);
|
||||
if (husk.Info != null)
|
||||
{
|
||||
husk.Info.Character = husk;
|
||||
husk.Info.TeamID = Character.TeamType.None;
|
||||
}
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
|
||||
@@ -399,7 +399,7 @@ namespace Barotrauma
|
||||
|
||||
public void ApplyAffliction(Limb targetLimb, Affliction affliction)
|
||||
{
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
if (!affliction.Prefab.IsBuff && Unkillable || Character.GodMode) { return; }
|
||||
if (affliction.Prefab.LimbSpecific)
|
||||
{
|
||||
if (targetLimb == null)
|
||||
|
||||
@@ -61,11 +61,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("None", false)]
|
||||
[Serialize(CampaignMode.InteractionType.None, false)]
|
||||
public CampaignMode.InteractionType CampaignInteractionType { get; protected set; }
|
||||
|
||||
[Serialize("Passive", false)]
|
||||
public AIObjectiveIdle.BehaviorType BehaviorType { get; protected set; }
|
||||
[Serialize(AIObjectiveIdle.BehaviorType.Passive, false)]
|
||||
public AIObjectiveIdle.BehaviorType Behavior { get; protected set; }
|
||||
|
||||
public List<string> PreferredOutpostModuleTypes { get; protected set; }
|
||||
|
||||
@@ -163,6 +163,13 @@ namespace Barotrauma
|
||||
{
|
||||
item.AddTag("job:" + job.Name);
|
||||
}
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
if (idCardComponent != null)
|
||||
{
|
||||
idCardComponent.Initialize(character.Info);
|
||||
}
|
||||
|
||||
var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in idCardTags)
|
||||
{
|
||||
|
||||
@@ -197,6 +197,12 @@ namespace Barotrauma
|
||||
item.AddTag("job:" + Name);
|
||||
if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc))
|
||||
item.Description = spawnPoint.IdCardDesc;
|
||||
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
if (idCardComponent != null)
|
||||
{
|
||||
idCardComponent.Initialize(character.Info);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
|
||||
@@ -90,6 +90,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(AIObjectiveIdle.BehaviorType.Passive, false)]
|
||||
public AIObjectiveIdle.BehaviorType IdleBehavior
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string OriginalName { get { return Identifier; } }
|
||||
|
||||
public ContentPackage ContentPackage { get; private set; }
|
||||
|
||||
@@ -223,7 +223,29 @@ namespace Barotrauma
|
||||
|
||||
public readonly LimbType type;
|
||||
|
||||
public readonly bool ignoreCollisions;
|
||||
private bool ignoreCollisions;
|
||||
public bool IgnoreCollisions
|
||||
{
|
||||
get { return ignoreCollisions; }
|
||||
set
|
||||
{
|
||||
ignoreCollisions = value;
|
||||
if (body != null)
|
||||
{
|
||||
if (ignoreCollisions)
|
||||
{
|
||||
body.CollisionCategories = Category.None;
|
||||
body.CollidesWith = Category.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
//limbs don't collide with each other
|
||||
body.CollisionCategories = Physics.CollisionCharacter;
|
||||
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool isSevered;
|
||||
private float severedFadeOutTimer;
|
||||
@@ -310,6 +332,12 @@ namespace Barotrauma
|
||||
|
||||
public Submarine Submarine => character.Submarine;
|
||||
|
||||
public bool Hidden
|
||||
{
|
||||
get => Params.Hide;
|
||||
set => Params.Hide = value;
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }
|
||||
@@ -549,7 +577,7 @@ namespace Barotrauma
|
||||
{
|
||||
body.CollisionCategories = Category.None;
|
||||
body.CollidesWith = Category.None;
|
||||
ignoreCollisions = true;
|
||||
IgnoreCollisions = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -763,27 +791,56 @@ namespace Barotrauma
|
||||
severedFadeOutTimer = SeveredFadeOutTime;
|
||||
}
|
||||
}
|
||||
else if (!IsDead)
|
||||
{
|
||||
if (Params.BlinkFrequency > 0)
|
||||
{
|
||||
if (blinkTimer > -TotalBlinkDurationOut)
|
||||
{
|
||||
blinkTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
blinkTimer = Params.BlinkFrequency;
|
||||
}
|
||||
}
|
||||
if (reEnableTimer > 0)
|
||||
{
|
||||
reEnableTimer -= deltaTime;
|
||||
}
|
||||
else if (reEnableTimer > -1)
|
||||
{
|
||||
ReEnable();
|
||||
}
|
||||
}
|
||||
|
||||
if (attack != null)
|
||||
{
|
||||
attack.UpdateCoolDown(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (Params.BlinkFrequency > 0)
|
||||
private float reEnableTimer = -1;
|
||||
public void HideAndDisable(float duration = 0)
|
||||
{
|
||||
Hidden = true;
|
||||
Disabled = true;
|
||||
IgnoreCollisions = true;
|
||||
if (duration > 0)
|
||||
{
|
||||
if (blinkTimer > -TotalBlinkDurationOut)
|
||||
{
|
||||
blinkTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
blinkTimer = Params.BlinkFrequency;
|
||||
}
|
||||
reEnableTimer = duration;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
private void ReEnable()
|
||||
{
|
||||
Hidden = false;
|
||||
Disabled = false;
|
||||
IgnoreCollisions = false;
|
||||
reEnableTimer = -1;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
private readonly List<Body> contactBodies = new List<Body>();
|
||||
/// <summary>
|
||||
@@ -942,7 +999,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
if (damageTarget is Character targetCharacter && targetLimb != null)
|
||||
{
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound);
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -952,7 +1009,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound);
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body);
|
||||
}
|
||||
}
|
||||
/*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
|
||||
|
||||
@@ -492,8 +492,6 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable()]
|
||||
public bool RandomAttack { get; private set; }
|
||||
|
||||
// TODO: latchonto, swarming
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
protected readonly List<TargetParams> targets = new List<TargetParams>();
|
||||
|
||||
@@ -589,6 +587,21 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "Should the target be ignored if it's inside a container/inventory. Only affects items."), Editable]
|
||||
public bool IgnoreContained { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the target be ignored while the creature is inside. Doesn't matter where the target is."), Editable]
|
||||
public bool IgnoreWhileInside { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the target be ignored while the creature is outside. Doesn't matter where the target is."), Editable]
|
||||
public bool IgnoreWhileOutside { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "Use to define a distance at which the creature starts the sweeping movement."), Editable(MinValueFloat = 0, MaxValueFloat = 10000, ValueStep = 1, DecimalCount = 0)]
|
||||
public float SweepDistance { get; private set; }
|
||||
|
||||
[Serialize(10f, true, description: "How much the sweep affects the steering?"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1f, DecimalCount = 1)]
|
||||
public float SweepStrength { get; private set; }
|
||||
|
||||
[Serialize(1f, true, description: "How quickly the sweep direction changes. Uses the sine wave pattern."), Editable(MinValueFloat = 0, MaxValueFloat = 10, ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float SweepSpeed { get; private set; }
|
||||
|
||||
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
|
||||
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
|
||||
|
||||
@@ -745,7 +745,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (LightSource != null) { return false; }
|
||||
var lightSourceElement = new XElement("lightsource",
|
||||
new XElement("lighttexture", new XAttribute("texture", "Content/Lights/light.png")));
|
||||
new XElement("lighttexture", new XAttribute("texture", "Content/Lights/pointlight_bright.png")));
|
||||
TryAddSubParam(lightSourceElement, (e, c) => new LightSourceParams(e, c), out LightSourceParams newLightSource);
|
||||
LightSource = newLightSource;
|
||||
return LightSource != null;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Barotrauma
|
||||
Outpost,
|
||||
OutpostModule,
|
||||
OutpostConfig,
|
||||
BeaconStation,
|
||||
NPCSets,
|
||||
Factions,
|
||||
Text,
|
||||
@@ -28,6 +29,7 @@ namespace Barotrauma
|
||||
LocationTypes,
|
||||
MapGenerationParameters,
|
||||
LevelGenerationParameters,
|
||||
CaveGenerationParameters,
|
||||
LevelObjectPrefabs,
|
||||
RandomEvents,
|
||||
Missions,
|
||||
@@ -47,7 +49,8 @@ namespace Barotrauma
|
||||
Wreck,
|
||||
Corpses,
|
||||
WreckAIConfig,
|
||||
UpgradeModules
|
||||
UpgradeModules,
|
||||
MapCreature
|
||||
}
|
||||
|
||||
public class ContentPackage
|
||||
@@ -84,6 +87,7 @@ namespace Barotrauma
|
||||
ContentType.Factions,
|
||||
ContentType.MapGenerationParameters,
|
||||
ContentType.LevelGenerationParameters,
|
||||
ContentType.CaveGenerationParameters,
|
||||
ContentType.Missions,
|
||||
ContentType.LevelObjectPrefabs,
|
||||
ContentType.RuinConfig,
|
||||
@@ -92,10 +96,12 @@ namespace Barotrauma
|
||||
ContentType.OutpostConfig,
|
||||
ContentType.Wreck,
|
||||
ContentType.WreckAIConfig,
|
||||
ContentType.BeaconStation,
|
||||
ContentType.Afflictions,
|
||||
ContentType.Orders,
|
||||
ContentType.Corpses,
|
||||
ContentType.UpgradeModules
|
||||
ContentType.UpgradeModules,
|
||||
ContentType.MapCreature
|
||||
};
|
||||
|
||||
//at least one file of each these types is required in core content packages
|
||||
@@ -111,11 +117,13 @@ namespace Barotrauma
|
||||
ContentType.Factions,
|
||||
ContentType.Wreck,
|
||||
ContentType.WreckAIConfig,
|
||||
ContentType.BeaconStation,
|
||||
ContentType.Text,
|
||||
ContentType.ServerExecutable,
|
||||
ContentType.LocationTypes,
|
||||
ContentType.MapGenerationParameters,
|
||||
ContentType.LevelGenerationParameters,
|
||||
ContentType.CaveGenerationParameters,
|
||||
ContentType.RandomEvents,
|
||||
ContentType.Missions,
|
||||
ContentType.RuinConfig,
|
||||
@@ -384,6 +392,7 @@ namespace Barotrauma
|
||||
case ContentType.OutpostModule:
|
||||
case ContentType.Submarine:
|
||||
case ContentType.Wreck:
|
||||
case ContentType.BeaconStation:
|
||||
break;
|
||||
default:
|
||||
try
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Globalization;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -1160,8 +1161,10 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!(c.AIController is EnemyAIController)) continue;
|
||||
c.SetAllDamage(200.0f, 0.0f, 0.0f);
|
||||
if (c.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior == null)
|
||||
{
|
||||
c.SetAllDamage(200.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
}, null, isCheat: true));
|
||||
|
||||
@@ -1259,6 +1262,75 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("ballastflora", "infectballast [options]: Infect ballasts and control its growth.", args =>
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ThrowError("No action specified.");
|
||||
return;
|
||||
}
|
||||
|
||||
string primaryAction = args.Length > 0 ? args[0] : "";
|
||||
string secondaryArgument = args.Length > 1 ? args[1] : "";
|
||||
|
||||
if (Submarine.MainSub == null)
|
||||
{
|
||||
ThrowError("No submarine loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (primaryAction.Equals("infect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
List<Pump> pumps = new List<Pump>();
|
||||
foreach (Item item in Submarine.MainSub.GetItems(true))
|
||||
{
|
||||
if (item.CurrentHull != null && item.HasTag("ballast") && item.GetComponent<Pump>() is { } pump)
|
||||
{
|
||||
pumps.Add(pump);
|
||||
}
|
||||
}
|
||||
|
||||
if (pumps.Any())
|
||||
{
|
||||
BallastFloraPrefab prefab = string.IsNullOrWhiteSpace(secondaryArgument) ? BallastFloraPrefab.Prefabs.First() : BallastFloraPrefab.Find(secondaryArgument);
|
||||
if (prefab == null)
|
||||
{
|
||||
ThrowError($"No such behavior: {secondaryArgument}");
|
||||
return;
|
||||
}
|
||||
|
||||
Pump random = pumps.GetRandom();
|
||||
random.InfectBallast(prefab.Identifier);
|
||||
NewMessage($"Infected {random.Name} with {prefab.Identifier}.", Color.Green);
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowError("No available pumps to infect on this submarine.");
|
||||
}
|
||||
|
||||
if (primaryAction.Equals("growthwarp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (int.TryParse(secondaryArgument, out int value))
|
||||
{
|
||||
foreach (Hull hull in Hull.hullList.Where(h => h.BallastFlora != null))
|
||||
{
|
||||
BallastFloraBehavior bs = hull.BallastFlora;
|
||||
bs.GrowthWarps = value;
|
||||
}
|
||||
|
||||
NewMessage("Accelerating growth...", Color.Green);
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowError($"Invalid integer \"{secondaryArgument}\".");
|
||||
}
|
||||
}, isCheat: true, getValidArgs: () =>
|
||||
{
|
||||
string[] primaries = { "infect", "growthwarp" };
|
||||
string[] identifiers = BallastFloraPrefab.Prefabs.Select(bfp => bfp.Identifier).Distinct().ToArray();
|
||||
return new[] { primaries, identifiers };
|
||||
}));
|
||||
|
||||
commands.Add(new Command("difficulty|leveldifficulty", "difficulty [0-100]: Change the level difficulty setting in the server lobby.", null));
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@ namespace Barotrauma
|
||||
public override void Init(bool affectSubImmediately)
|
||||
{
|
||||
spawnPos = Level.Loaded.GetRandomItemPos(
|
||||
(Rand.Value(Rand.RandSync.Server) < 0.5f) ? Level.PositionType.MainPath : Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
(Rand.Value(Rand.RandSync.Server) < 0.5f) ?
|
||||
Level.PositionType.MainPath | Level.PositionType.SidePath :
|
||||
Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
500.0f, 10000.0f, 30.0f);
|
||||
|
||||
spawnPending = true;
|
||||
|
||||
@@ -9,7 +9,17 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, true)]
|
||||
public float Chance { get; set; }
|
||||
|
||||
public RNGAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
public RNGAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (Chance >= 1.0f)
|
||||
{
|
||||
DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 1.0 (100%) or more, the action will always succeed.");
|
||||
}
|
||||
else if (Chance <= 0.0f)
|
||||
{
|
||||
DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 0 or less, the action will never succeed.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, true)]
|
||||
public float RequiredLevel { get; set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool ProbabilityBased { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
@@ -27,7 +30,15 @@ namespace Barotrauma
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var potentialTargets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character);
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel);
|
||||
|
||||
if (ProbabilityBased)
|
||||
{
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) / RequiredLevel > Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced));
|
||||
}
|
||||
else
|
||||
{
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace Barotrauma
|
||||
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
if (idleObjective != null)
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.BehaviorType;
|
||||
idleObjective.Behavior = humanPrefab.Behavior;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
|
||||
@@ -107,7 +107,13 @@ namespace Barotrauma
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
CreateEvents(initialEventSet);
|
||||
int seed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed ^= ToolBox.StringToInt(previousEvent.Identifier);
|
||||
}
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
CreateEvents(initialEventSet, rand);
|
||||
}
|
||||
|
||||
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
|
||||
@@ -325,7 +331,7 @@ namespace Barotrauma
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void CreateEvents(EventSet eventSet)
|
||||
private void CreateEvents(EventSet eventSet, Random rand)
|
||||
{
|
||||
if (level == null) { return; }
|
||||
int applyCount = 1;
|
||||
@@ -343,13 +349,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
{
|
||||
int seed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed |= ToolBox.StringToInt(previousEvent.Identifier);
|
||||
}
|
||||
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(eventSet.EventPrefabs);
|
||||
for (int j = 0; j < eventSet.EventCount; j++)
|
||||
{
|
||||
@@ -371,7 +370,7 @@ namespace Barotrauma
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) { CreateEvents(newEventSet); }
|
||||
if (newEventSet != null) { CreateEvents(newEventSet, rand); }
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -390,7 +389,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
CreateEvents(childEventSet);
|
||||
CreateEvents(childEventSet, rand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,7 +582,7 @@ namespace Barotrauma
|
||||
enemyDanger = 0.0f;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead || character.IsIncapacitated || !character.Enabled) continue;
|
||||
if (character.IsDead || character.IsIncapacitated || !character.Enabled || character.IsPet || character.Params.CompareGroup("human")) { continue; }
|
||||
|
||||
EnemyAIController enemyAI = character.AIController as EnemyAIController;
|
||||
if (enemyAI == null) continue;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class BeaconMission : Mission
|
||||
{
|
||||
private bool swarmSpawned;
|
||||
private string monsterSpeciesName;
|
||||
private Point monsterCountRange;
|
||||
private Level level;
|
||||
private Location[] locations;
|
||||
private string sonarLabel;
|
||||
|
||||
public BeaconMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
|
||||
{
|
||||
swarmSpawned = false;
|
||||
|
||||
XElement monsterElement = prefab.ConfigElement.Element("monster");
|
||||
|
||||
monsterSpeciesName = monsterElement.GetAttributeString("character", string.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
defaultCount = monsterElement.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
|
||||
|
||||
monsterCountRange = new Point(min, max);
|
||||
|
||||
this.locations = locations;
|
||||
|
||||
sonarLabel = TextManager.Get("beaconstationsonarlabel");
|
||||
}
|
||||
|
||||
public override string SonarLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.IsNullOrEmpty(base.SonarLabel) ? sonarLabel : base.SonarLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return level.BeaconStation.WorldPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
if (!swarmSpawned && level.CheckBeaconActive())
|
||||
{
|
||||
State = 1;
|
||||
Vector2 spawnPos = level.BeaconStation.WorldPosition;
|
||||
spawnPos.Y += level.BeaconStation.GetDockedBorders().Height * 1.5f;
|
||||
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(monsterSpeciesName, spawnPos);
|
||||
}
|
||||
swarmSpawned = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
completed = level.CheckBeaconActive();
|
||||
if (completed)
|
||||
{
|
||||
if (GameMain.GameSession.GameMode is CampaignMode)
|
||||
{
|
||||
int naturalFormationIndex = locations[0].Type.Identifier.Equals("None", StringComparison.OrdinalIgnoreCase) ? 0 : 1;
|
||||
var upgradeLocation = locations[naturalFormationIndex];
|
||||
upgradeLocation.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals("Explored", StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
GiveReward();
|
||||
}
|
||||
}
|
||||
|
||||
public override void AdjustLevelData(LevelData levelData)
|
||||
{
|
||||
levelData.HasBeaconStation = true;
|
||||
levelData.IsBeaconActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MineralMission : Mission
|
||||
{
|
||||
private Dictionary<string, Pair<int, float>> ResourceClusters { get; } = new Dictionary<string, Pair<int, float>>();
|
||||
private Dictionary<string, List<Item>> SpawnedResources { get; } = new Dictionary<string, List<Item>>();
|
||||
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>();
|
||||
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
return MissionClusterPositions
|
||||
.Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1]))
|
||||
.Select(p => p.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public MineralMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
|
||||
{
|
||||
var configElement = prefab.ConfigElement.Element("Items");
|
||||
foreach (var c in configElement.GetChildElements("Item"))
|
||||
{
|
||||
var identifier = c.GetAttributeString("identifier", null);
|
||||
if (string.IsNullOrWhiteSpace(identifier)) { continue; }
|
||||
if (ResourceClusters.ContainsKey(identifier))
|
||||
{
|
||||
ResourceClusters[identifier].First++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResourceClusters.Add(identifier, new Pair<int, float>(1, 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
foreach (var kvp in ResourceClusters)
|
||||
{
|
||||
var prefab = ItemPrefab.Find(null, kvp.Key);
|
||||
if (prefab == null) { continue; }
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation);
|
||||
if (spawnedResources.None()) { continue; }
|
||||
SpawnedResources.Add(kvp.Key, spawnedResources);
|
||||
kvp.Value.Second = rotation;
|
||||
}
|
||||
CalculateMissionClusterPositions();
|
||||
FindRelevantLevelResources();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (!EnoughHaveBeenCollected()) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (!EnoughHaveBeenCollected()) { return; }
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
|
||||
private void FindRelevantLevelResources()
|
||||
{
|
||||
RelevantLevelResources.Clear();
|
||||
foreach (var identifier in ResourceClusters.Keys)
|
||||
{
|
||||
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
|
||||
i.Submarine == null && i.ParentInventory == null &&
|
||||
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
|
||||
.ToArray();
|
||||
RelevantLevelResources.Add(identifier, items);
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnoughHaveBeenCollected()
|
||||
{
|
||||
foreach (var kvp in ResourceClusters)
|
||||
{
|
||||
if (RelevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
|
||||
{
|
||||
var collected = availableResources.Count(r => HasBeenCollected(r));
|
||||
var needed = kvp.Value.First;
|
||||
if (collected < needed) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HasBeenCollected(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.Removed) { return false; }
|
||||
var owner = item.GetRootInventoryOwner();
|
||||
if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (owner is Character c)
|
||||
{
|
||||
return c.Info != null && GameMain.GameSession.CrewManager.CharacterInfos.Contains(c.Info);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AnyAreUncollected(IEnumerable<Item> items)
|
||||
=> items.Any(i => !HasBeenCollected(i));
|
||||
|
||||
private void CalculateMissionClusterPositions()
|
||||
{
|
||||
MissionClusterPositions.Clear();
|
||||
foreach (var kvp in SpawnedResources)
|
||||
{
|
||||
if (kvp.Value.None()) { continue; }
|
||||
var pos = Vector2.Zero;
|
||||
var itemCount = 0;
|
||||
foreach (var i in kvp.Value.Where(i => i != null && !i.Removed))
|
||||
{
|
||||
pos += i.WorldPosition;
|
||||
itemCount++;
|
||||
}
|
||||
pos /= itemCount;
|
||||
MissionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma
|
||||
get { return Enumerable.Empty<Vector2>(); }
|
||||
}
|
||||
|
||||
public string SonarLabel
|
||||
public virtual string SonarLabel
|
||||
{
|
||||
get { return Prefab.SonarLabel; }
|
||||
}
|
||||
@@ -233,5 +233,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AdjustLevelData(LevelData levelData) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,29 @@ namespace Barotrauma
|
||||
Salvage = 0x1,
|
||||
Monster = 0x2,
|
||||
Cargo = 0x4,
|
||||
Combat = 0x8,
|
||||
All = 0xf
|
||||
Beacon = 0x8,
|
||||
Nest = 0x10,
|
||||
Mineral = 0x20,
|
||||
Combat = 0x40,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
{
|
||||
public static readonly List<MissionPrefab> List = new List<MissionPrefab>();
|
||||
|
||||
private static readonly Dictionary<MissionType, Type> missionClasses = new Dictionary<MissionType, Type>()
|
||||
public static readonly Dictionary<MissionType, Type> CoOpMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Salvage, typeof(SalvageMission) },
|
||||
{ MissionType.Monster, typeof(MonsterMission) },
|
||||
{ MissionType.Cargo, typeof(CargoMission) },
|
||||
{ MissionType.Combat, typeof(CombatMission) },
|
||||
{ MissionType.Beacon, typeof(BeaconMission) },
|
||||
{ MissionType.Nest, typeof(NestMission) },
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
};
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
@@ -146,15 +155,32 @@ namespace Barotrauma
|
||||
Headers = new List<string>();
|
||||
Messages = new List<string>();
|
||||
AllowedLocationTypes = new List<Pair<string, string>>();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
string header = TextManager.Get("MissionHeader" + i + "." + TextIdentifier, true);
|
||||
string message = TextManager.Get("MissionMessage" + i + "." + TextIdentifier, true);
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
Headers.Add(header);
|
||||
Messages.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
int messageIndex = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "message":
|
||||
int index = Messages.Count;
|
||||
|
||||
Headers.Add(TextManager.Get("MissionHeader" + index + "." + TextIdentifier, true) ?? subElement.GetAttributeString("header", ""));
|
||||
Messages.Add(TextManager.Get("MissionMessage" + index + "." + TextIdentifier, true) ?? subElement.GetAttributeString("text", ""));
|
||||
if (messageIndex > Headers.Count - 1)
|
||||
{
|
||||
Headers.Add(string.Empty);
|
||||
Messages.Add(string.Empty);
|
||||
}
|
||||
Headers[messageIndex] = TextManager.Get("MissionHeader" + messageIndex + "." + TextIdentifier, true) ?? subElement.GetAttributeString("header", "");
|
||||
Messages[messageIndex] = TextManager.Get("MissionMessage" + messageIndex + "." + TextIdentifier, true) ?? subElement.GetAttributeString("text", "");
|
||||
messageIndex++;
|
||||
break;
|
||||
case "locationtype":
|
||||
AllowedLocationTypes.Add(new Pair<string, string>(
|
||||
@@ -211,7 +237,18 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
constructor = missionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
if (CoOpMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = CoOpMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
}
|
||||
else if (PvPMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = PvPMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - unsupported mission type \"" + Type.ToString() + "\"");
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Barotrauma
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
foreach (var monster in monsterPrefabs)
|
||||
{
|
||||
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class NestMission : Mission
|
||||
{
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
private readonly Dictionary<Item, StatusEffect> statusEffectOnApproach = new Dictionary<Item, StatusEffect>();
|
||||
|
||||
//string = filename, point = min,max
|
||||
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
|
||||
|
||||
private readonly float itemSpawnRadius = 800.0f;
|
||||
private readonly float approachItemsRadius = 1000.0f;
|
||||
private readonly float monsterSpawnRadius = 3000.0f;
|
||||
|
||||
private readonly bool requireDelivery;
|
||||
|
||||
private readonly Level.PositionType spawnPositionType;
|
||||
|
||||
private Vector2 nestPosition;
|
||||
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return nestPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public NestMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
|
||||
itemSpawnRadius = prefab.ConfigElement.GetAttributeFloat("itemspawnradius", 800.0f);
|
||||
approachItemsRadius = prefab.ConfigElement.GetAttributeFloat("approachitemsradius", itemSpawnRadius * 2.0f);
|
||||
monsterSpawnRadius = prefab.ConfigElement.GetAttributeFloat("monsterspawnradius", approachItemsRadius * 2.0f);
|
||||
|
||||
requireDelivery = prefab.ConfigElement.GetAttributeBool("requiredelivery", false);
|
||||
|
||||
string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
|
||||
!Enum.TryParse(spawnPositionTypeStr, true, out spawnPositionType))
|
||||
{
|
||||
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
|
||||
}
|
||||
|
||||
|
||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
string speciesName = monsterElement.GetAttributeString("character", string.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
defaultCount = monsterElement.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab != null)
|
||||
{
|
||||
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
//ruin/cave/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Cave || spawnPositionType == Level.PositionType.Wreck ?
|
||||
0.0f : Level.Loaded.Size.X * 0.3f;
|
||||
nestPosition = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
List<GraphEdge> spawnEdges = new List<GraphEdge>();
|
||||
if (spawnPositionType == Level.PositionType.Cave)
|
||||
{
|
||||
var nearbyCells = Level.Loaded.GetCells(nestPosition, searchDepth: 3);
|
||||
if (nearbyCells.Any())
|
||||
{
|
||||
List<GraphEdge> validEdges = new List<GraphEdge>();
|
||||
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
if (!edge.NextToCave || !edge.IsSolid) { continue; }
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(edge.Center + edge.GetNormal(edge.Cell1 ?? edge.Cell2) * 100.0f))) { continue; }
|
||||
validEdges.Add(edge);
|
||||
}
|
||||
|
||||
if (validEdges.Any())
|
||||
{
|
||||
spawnEdges.AddRange(validEdges.Where(e => MathUtils.LineSegmentToPointDistanceSquared(e.Point1.ToPoint(), e.Point2.ToPoint(), nestPosition.ToPoint()) < itemSpawnRadius * itemSpawnRadius).Distinct());
|
||||
}
|
||||
//no valid edges found close enough to the nest position, find the closest one
|
||||
if (!spawnEdges.Any())
|
||||
{
|
||||
GraphEdge closestEdge = null;
|
||||
float closestDist = float.PositiveInfinity;
|
||||
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
if (!edge.NextToCave || !edge.IsSolid) { continue; }
|
||||
float dist = Vector2.DistanceSquared(edge.Center, nestPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestEdge = edge;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
if (closestEdge != null)
|
||||
{
|
||||
spawnEdges.Add(closestEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 spawnPos = nestPosition;
|
||||
float rotation = 0.0f;
|
||||
if (spawnEdges.Any())
|
||||
{
|
||||
var edge = spawnEdges.GetRandom(Rand.RandSync.Server);
|
||||
spawnPos = Vector2.Lerp(edge.Point1, edge.Point2, Rand.Range(0.1f, 0.9f, Rand.RandSync.Server));
|
||||
Vector2 normal = Vector2.UnitY;
|
||||
if (edge.Cell1 != null && edge.Cell1.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell1);
|
||||
}
|
||||
else if (edge.Cell2 != null && edge.Cell2.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell2);
|
||||
}
|
||||
spawnPos += normal * 10.0f;
|
||||
rotation = MathUtils.VectorToAngle(normal) - MathHelper.PiOver2;
|
||||
}
|
||||
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
|
||||
item.FindHull();
|
||||
items.Add(item);
|
||||
|
||||
var statusEffectElement = subElement.Element("StatusEffectOnApproach") ?? subElement.Element("statuseffectonapproach");
|
||||
if (statusEffectElement != null)
|
||||
{
|
||||
statusEffectOnApproach.Add(item, StatusEffect.Load(statusEffectElement, Prefab.Identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient)
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (statusEffectOnApproach.ContainsKey(item))
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsPlayer && Vector2.DistanceSquared(nestPosition, character.WorldPosition) < approachItemsRadius * approachItemsRadius)
|
||||
{
|
||||
statusEffectOnApproach[item].Apply(statusEffectOnApproach[item].type, 1.0f, item, item);
|
||||
statusEffectOnApproach.Remove(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monsterPrefabs.Any())
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsPlayer && Vector2.DistanceSquared(nestPosition, character.WorldPosition) < monsterSpawnRadius * monsterSpawnRadius)
|
||||
{
|
||||
foreach (var monster in monsterPrefabs)
|
||||
{
|
||||
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
Character.Create(monster.Item1.Identifier, nestPosition + Rand.Vector(100.0f), ToolBox.RandomSeed(8), createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
monsterPrefabs.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//continue when all items are in the sub or destroyed
|
||||
if (AllItemsDestroyedOrRetrieved()) { State = 1; }
|
||||
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllItemsDestroyedOrRetrieved()
|
||||
{
|
||||
if (requireDelivery)
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub?.Info?.Type == SubmarineType.Player) { continue; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.Removed || item.Condition <= 0.0f) { continue; }
|
||||
if (Vector2.Distance(item.WorldPosition, nestPosition) > Math.Max(itemSpawnRadius * 2, 3000.0f)) { continue; }
|
||||
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub?.Info?.Type == SubmarineType.Player) { continue; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (!AllItemsDestroyedOrRetrieved())
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item != null && !item.Removed)
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
items.Clear();
|
||||
GiveReward();
|
||||
completed = true;
|
||||
failed = !completed && state > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,8 @@ namespace Barotrauma
|
||||
item = null;
|
||||
if (!IsClient)
|
||||
{
|
||||
//ruin/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Wreck ?
|
||||
//ruin/cave/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Cave || spawnPositionType == Level.PositionType.Wreck ?
|
||||
0.0f : Level.Loaded.Size.X * 0.3f;
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Barotrauma
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
case Level.PositionType.SidePath:
|
||||
item = suitableItems.FirstOrDefault(it => Vector2.DistanceSquared(it.WorldPosition, position) < 1000.0f);
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
|
||||
@@ -149,7 +149,11 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (position.PositionType != Level.PositionType.MainPath) { continue; }
|
||||
if (position.PositionType != Level.PositionType.MainPath &&
|
||||
position.PositionType != Level.PositionType.SidePath)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(position.Position.ToVector2()))))
|
||||
{
|
||||
removals.Add(position);
|
||||
@@ -281,7 +285,8 @@ namespace Barotrauma
|
||||
spawnPos = spawnPoint.WorldPosition;
|
||||
}
|
||||
}
|
||||
else if (chosenPosition.PositionType == Level.PositionType.MainPath && offset > 0)
|
||||
else if ((chosenPosition.PositionType == Level.PositionType.MainPath || chosenPosition.PositionType == Level.PositionType.SidePath)
|
||||
&& offset > 0)
|
||||
{
|
||||
Vector2 dir;
|
||||
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null);
|
||||
@@ -381,9 +386,10 @@ namespace Barotrauma
|
||||
//+1 because Range returns an integer less than the max value
|
||||
int amount = Rand.Range(minAmount, maxAmount + 1);
|
||||
monsters = new List<Character>();
|
||||
float offsetAmount = spawnPosType == Level.PositionType.MainPath ? scatter : 100;
|
||||
float offsetAmount = spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath ? scatter : 100;
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
string seed = Level.Loaded.Seed + i.ToString();
|
||||
CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
@@ -392,7 +398,7 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
|
||||
|
||||
Vector2 pos = spawnPos.Value + Rand.Vector(offsetAmount);
|
||||
if (spawnPosType == Level.PositionType.MainPath)
|
||||
if (spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath)
|
||||
{
|
||||
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
|
||||
{
|
||||
@@ -406,7 +412,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
monsters.Add(Character.Create(speciesName, pos, Level.Loaded.Seed + i.ToString(), null, false, true, true));
|
||||
monsters.Add(Character.Create(speciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true));
|
||||
|
||||
if (monsters.Count == amount)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (string word in words)
|
||||
{
|
||||
if (forbiddenWords.Any(w => Homoglyphs.Compare(word, w) || Homoglyphs.Compare(word + 's', w)))
|
||||
if (forbiddenWords.Any(w => Homoglyphs.Compare(word, w)))
|
||||
{
|
||||
forbiddenWord = word;
|
||||
return true;
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace Barotrauma
|
||||
{
|
||||
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
|
||||
OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
|
||||
OriginalContainerID = validContainer.Key.Item.OriginalID
|
||||
OriginalContainerID = validContainer.Key.Item.ID
|
||||
};
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
|
||||
@@ -217,7 +217,7 @@ namespace Barotrauma
|
||||
|
||||
if (containerPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + containerPrefab.Name + "\"!");
|
||||
DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,11 @@ namespace Barotrauma
|
||||
|
||||
public bool HasBots { get; set; }
|
||||
|
||||
public List<Pair<Order, float>> ActiveOrders { get; } = new List<Pair<Order, float>>();
|
||||
public List<Pair<Order, float?>> ActiveOrders { get; } = new List<Pair<Order, float?>>();
|
||||
public bool IsSinglePlayer { get; private set; }
|
||||
|
||||
public ReadyCheck ActiveReadyCheck;
|
||||
|
||||
public CrewManager(bool isSinglePlayer)
|
||||
{
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
@@ -38,7 +40,7 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjectSpecific();
|
||||
|
||||
public bool AddOrder(Order order, float fadeOutTime)
|
||||
public bool AddOrder(Order order, float? fadeOutTime)
|
||||
{
|
||||
if (order.TargetEntity == null)
|
||||
{
|
||||
@@ -46,7 +48,10 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
Pair<Order, float> existingOrder = ActiveOrders.Find(o => o.First.Prefab == order.Prefab && o.First.TargetEntity == order.TargetEntity);
|
||||
Pair<Order, float?> existingOrder =
|
||||
ActiveOrders.Find(o => o.First.Prefab == order.Prefab && o.First.TargetEntity == order.TargetEntity &&
|
||||
(o.First.TargetType != Order.OrderTargetType.WallSection || o.First.WallSectionIndex == order.WallSectionIndex));
|
||||
|
||||
if (existingOrder != null)
|
||||
{
|
||||
existingOrder.Second = fadeOutTime;
|
||||
@@ -54,32 +59,33 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveOrders.Add(new Pair<Order, float>(order, fadeOutTime));
|
||||
ActiveOrders.Add(new Pair<Order, float?>(order, fadeOutTime));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveOrder(Order order)
|
||||
{
|
||||
ActiveOrders.RemoveAll(o => o.First == order);
|
||||
}
|
||||
|
||||
public void AddCharacterElements(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (XElement characterElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (!characterElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
CharacterInfo characterInfo = new CharacterInfo(subElement);
|
||||
CharacterInfo characterInfo = new CharacterInfo(characterElement);
|
||||
#if CLIENT
|
||||
if (subElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; }
|
||||
if (characterElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; }
|
||||
#endif
|
||||
characterInfos.Add(characterInfo);
|
||||
foreach (XElement invElement in subElement.Elements())
|
||||
foreach (XElement subElement in characterElement.Elements())
|
||||
{
|
||||
if (!invElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
characterInfo.InventoryData = invElement;
|
||||
break;
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "inventory":
|
||||
characterInfo.InventoryData = subElement;
|
||||
break;
|
||||
case "health":
|
||||
characterInfo.HealthData = subElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +124,12 @@ namespace Barotrauma
|
||||
AddCharacterToCrewList(character);
|
||||
AddCurrentOrderIcon(character, character.CurrentOrder, character.CurrentOrderOption);
|
||||
#endif
|
||||
var idleObjective = character.AIController?.ObjectiveManager?.GetObjective<AIObjectiveIdle>();
|
||||
if (idleObjective != null)
|
||||
{
|
||||
idleObjective.Behavior = character.Info.Job.Prefab.IdleBehavior;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void AddCharacterInfo(CharacterInfo characterInfo)
|
||||
@@ -175,7 +187,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.Info.InventoryData != null)
|
||||
{
|
||||
character.Info.SpawnInventoryItems(character.Inventory, character.Info.InventoryData);
|
||||
character.SpawnInventoryItems(character.Inventory, character.Info.InventoryData);
|
||||
}
|
||||
else if (!character.Info.StartItemsGiven)
|
||||
{
|
||||
@@ -206,14 +218,19 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (Pair<Order, float> order in ActiveOrders)
|
||||
foreach (Pair<Order, float?> order in ActiveOrders)
|
||||
{
|
||||
order.Second -= deltaTime;
|
||||
if (order.Second.HasValue) { order.Second -= deltaTime; }
|
||||
}
|
||||
ActiveOrders.RemoveAll(o => o.Second <= 0.0f);
|
||||
ActiveOrders.RemoveAll(o => o.Second.HasValue && o.Second <= 0.0f);
|
||||
|
||||
UpdateConversations(deltaTime);
|
||||
UpdateProjectSpecific(deltaTime);
|
||||
ActiveReadyCheck?.Update(deltaTime);
|
||||
if (ActiveReadyCheck != null && ActiveReadyCheck.IsFinished)
|
||||
{
|
||||
ActiveReadyCheck = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Dialog
|
||||
|
||||
@@ -181,6 +181,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically cleared after triggering -> no need to unregister
|
||||
/// </summary>
|
||||
public event Action BeforeLevelLoading;
|
||||
|
||||
public void LoadNewLevel()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
@@ -194,6 +199,9 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
BeforeLevelLoading?.Invoke();
|
||||
BeforeLevelLoading = null;
|
||||
|
||||
if (Level.Loaded == null || Submarine.MainSub == null)
|
||||
{
|
||||
LoadInitialLevel();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CoOpMode : MissionMode
|
||||
{
|
||||
public CoOpMode(GameModePreset preset, MissionPrefab missionPrefab) : base(preset, ValidateMissionPrefab(missionPrefab, MissionPrefab.CoOpMissionClasses)) { }
|
||||
|
||||
public CoOpMode(GameModePreset preset, MissionType missionType, string seed) : base(preset, ValidateMissionType(missionType, MissionPrefab.CoOpMissionClasses), seed) { }
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public static GameModePreset MultiPlayerCampaign;
|
||||
public static GameModePreset Tutorial;
|
||||
public static GameModePreset Mission;
|
||||
public static GameModePreset PvP;
|
||||
public static GameModePreset TestMode;
|
||||
public static GameModePreset Sandbox;
|
||||
public static GameModePreset DevSandbox;
|
||||
@@ -51,7 +52,8 @@ namespace Barotrauma
|
||||
TestMode = new GameModePreset("testmode", typeof(TestGameMode), true);
|
||||
#endif
|
||||
Sandbox = new GameModePreset("sandbox", typeof(GameMode), false);
|
||||
Mission = new GameModePreset("mission", typeof(MissionMode), false);
|
||||
Mission = new GameModePreset("mission", typeof(CoOpMode), false);
|
||||
PvP = new GameModePreset("pvp", typeof(PvPMode), false);
|
||||
MultiPlayerCampaign = new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
namespace Barotrauma
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionMode : GameMode
|
||||
abstract partial class MissionMode : GameMode
|
||||
{
|
||||
private readonly Mission mission;
|
||||
|
||||
@@ -25,5 +28,29 @@
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
mission = Mission.LoadRandom(locations, seed, false, missionType);
|
||||
}
|
||||
|
||||
protected static MissionPrefab ValidateMissionPrefab(MissionPrefab missionPrefab, Dictionary<MissionType, Type> missionClasses)
|
||||
{
|
||||
if (ValidateMissionType(missionPrefab.Type, missionClasses) != missionPrefab.Type)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot start gamemode with mission type " + missionPrefab.Type);
|
||||
}
|
||||
return missionPrefab;
|
||||
}
|
||||
|
||||
protected static MissionType ValidateMissionType(MissionType missionType, Dictionary<MissionType, Type> missionClasses)
|
||||
{
|
||||
var missionTypes = (MissionType[])Enum.GetValues(typeof(MissionType));
|
||||
for (int i = 0; i < missionTypes.Length; i++)
|
||||
{
|
||||
var type = missionTypes[i];
|
||||
if (type == MissionType.None || type == MissionType.All) { continue; }
|
||||
if (!missionClasses.ContainsKey(type))
|
||||
{
|
||||
missionType &= ~(type);
|
||||
}
|
||||
}
|
||||
return missionType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,9 @@ namespace Barotrauma
|
||||
case "cargo":
|
||||
CargoManager?.LoadPurchasedItems(subElement);
|
||||
break;
|
||||
case "pets":
|
||||
petsElement = subElement;
|
||||
break;
|
||||
#if SERVER
|
||||
case "availablesubs":
|
||||
foreach (XElement availableSub in subElement.Elements())
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class PvPMode : MissionMode
|
||||
{
|
||||
public PvPMode(GameModePreset preset, MissionPrefab missionPrefab) : base(preset, ValidateMissionPrefab(missionPrefab, MissionPrefab.PvPMissionClasses)) { }
|
||||
|
||||
public PvPMode(GameModePreset preset, MissionType missionType, string seed) : base(preset, ValidateMissionType(missionType, MissionPrefab.PvPMissionClasses), seed) { }
|
||||
}
|
||||
}
|
||||
@@ -160,11 +160,17 @@ namespace Barotrauma
|
||||
|
||||
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, MissionPrefab missionPrefab = null, MissionType missionType = MissionType.None)
|
||||
{
|
||||
if (gameModePreset.GameModeType == typeof(MissionMode))
|
||||
if (gameModePreset.GameModeType == typeof(CoOpMode))
|
||||
{
|
||||
return missionPrefab != null ?
|
||||
new MissionMode(gameModePreset, missionPrefab) :
|
||||
new MissionMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
|
||||
new CoOpMode(gameModePreset, missionPrefab) :
|
||||
new CoOpMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(PvPMode))
|
||||
{
|
||||
return missionPrefab != null ?
|
||||
new PvPMode(gameModePreset, missionPrefab) :
|
||||
new PvPMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
|
||||
{
|
||||
@@ -382,7 +388,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Entity.Spawner = new EntitySpawner();
|
||||
|
||||
|
||||
if (GameMode.Mission != null) { Mission = GameMode.Mission; }
|
||||
if (GameMode != null) { GameMode.Start(); }
|
||||
if (GameMode.Mission != null)
|
||||
@@ -411,6 +417,7 @@ namespace Barotrauma
|
||||
//the server does this after loading the respawn shuttle
|
||||
Level?.SpawnNPCs();
|
||||
Level?.SpawnCorpses();
|
||||
Level?.PrepareBeaconStation();
|
||||
AutoItemPlacer.PlaceIfNeeded();
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal enum ReadyStatus
|
||||
{
|
||||
Unanswered,
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
internal partial class ReadyCheck
|
||||
{
|
||||
private readonly float endTime;
|
||||
private float time;
|
||||
public readonly Dictionary<byte, ReadyStatus> Clients;
|
||||
public bool IsFinished = false;
|
||||
|
||||
public ReadyCheck(List<byte> clients, float duration = 30)
|
||||
{
|
||||
Clients = new Dictionary<byte, ReadyStatus>();
|
||||
foreach (byte client in clients)
|
||||
{
|
||||
if (Clients.ContainsKey(client)) { continue; }
|
||||
|
||||
Clients.Add(client, ReadyStatus.Unanswered);
|
||||
}
|
||||
|
||||
time = duration;
|
||||
endTime = duration;
|
||||
#if CLIENT
|
||||
lastSecond = (int) Math.Ceiling(duration);
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void EndReadyCheck();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (time > 0)
|
||||
{
|
||||
#if CLIENT
|
||||
UpdateBar();
|
||||
#endif
|
||||
time -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
EndReadyCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -585,7 +585,8 @@ namespace Barotrauma
|
||||
if (files.Any(f => f.Type == ContentType.Submarine ||
|
||||
f.Type == ContentType.Outpost ||
|
||||
f.Type == ContentType.OutpostModule ||
|
||||
f.Type == ContentType.Wreck)) { SubmarineInfo.RefreshSavedSubs(); }
|
||||
f.Type == ContentType.Wreck ||
|
||||
f.Type == ContentType.BeaconStation)) { SubmarineInfo.RefreshSavedSubs(); }
|
||||
if (files.Any(f => f.Type == ContentType.NPCSets)) { NPCSet.LoadSets(); }
|
||||
if (files.Any(f => f.Type == ContentType.OutpostConfig)) { OutpostGenerationParams.LoadPresets(); }
|
||||
if (files.Any(f => f.Type == ContentType.Factions)) { FactionPrefab.LoadFactions(); }
|
||||
@@ -602,6 +603,7 @@ namespace Barotrauma
|
||||
if (files.Any(f => f.Type == ContentType.LevelObjectPrefabs)) { LevelObjectPrefab.LoadAll(); }
|
||||
if (files.Any(f => f.Type == ContentType.MapGenerationParameters)) { MapGenerationParams.Init(); }
|
||||
if (files.Any(f => f.Type == ContentType.LevelGenerationParameters)) { LevelGenerationParams.LoadPresets(); }
|
||||
if (files.Any(f => f.Type == ContentType.CaveGenerationParameters)) { CaveGenerationParams.LoadPresets(); }
|
||||
if (files.Any(f => f.Type == ContentType.TraitorMissions)) { TraitorMissionPrefab.Init(); }
|
||||
if (files.Any(f => f.Type == ContentType.Orders)) { Order.Init(); }
|
||||
if (files.Any(f => f.Type == ContentType.EventManagerSettings)) { EventManagerSettings.Init(); }
|
||||
@@ -635,6 +637,7 @@ namespace Barotrauma
|
||||
ContentType.LocationTypes,
|
||||
ContentType.MapGenerationParameters,
|
||||
ContentType.LevelGenerationParameters,
|
||||
ContentType.CaveGenerationParameters,
|
||||
ContentType.Sounds,
|
||||
ContentType.Particles,
|
||||
ContentType.Decals,
|
||||
@@ -645,6 +648,7 @@ namespace Barotrauma
|
||||
ContentType.Factions,
|
||||
ContentType.Wreck,
|
||||
ContentType.WreckAIConfig,
|
||||
ContentType.BeaconStation,
|
||||
ContentType.BackgroundCreaturePrefabs,
|
||||
ContentType.ServerExecutable,
|
||||
ContentType.TraitorMissions,
|
||||
@@ -834,7 +838,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefaultConfig(bool setLanguage = true)
|
||||
private void LoadDefaultConfig(bool setLanguage = true, bool loadContentPackages = true)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(SavePath);
|
||||
if (doc == null)
|
||||
@@ -866,7 +870,10 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
LoadControls(doc);
|
||||
#endif
|
||||
LoadContentPackages(doc);
|
||||
if (loadContentPackages)
|
||||
{
|
||||
LoadContentPackages(doc);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
WindowMode = WindowMode.Windowed;
|
||||
|
||||
@@ -89,6 +89,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically cleared after docking -> no need to unregister
|
||||
/// </summary>
|
||||
public event Action OnDocked;
|
||||
|
||||
/// <summary>
|
||||
/// Automatically cleared after undocking -> no need to unregister
|
||||
/// </summary>
|
||||
public event Action OnUnDocked;
|
||||
|
||||
public DockingPort(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -213,6 +223,9 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
OnDocked?.Invoke();
|
||||
OnDocked = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -817,6 +830,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
docked = false;
|
||||
|
||||
Item.Submarine.EnableObstructedWaypoints(DockingTarget.Item.Submarine);
|
||||
obstructedWayPointsDisabled = false;
|
||||
|
||||
DockingTarget.Undock();
|
||||
DockingTarget = null;
|
||||
|
||||
@@ -860,9 +876,6 @@ namespace Barotrauma.Items.Components
|
||||
outsideBlocker?.Body.Remove(outsideBlocker);
|
||||
outsideBlocker = null;
|
||||
|
||||
Item.Submarine.EnableObstructedWaypoints();
|
||||
obstructedWayPointsDisabled = false;
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true))
|
||||
{
|
||||
@@ -870,6 +883,8 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
OnUnDocked?.Invoke();
|
||||
OnUnDocked = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -1034,7 +1049,6 @@ namespace Barotrauma.Items.Components
|
||||
Dock(dockingPort);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
|
||||
@@ -23,6 +23,20 @@ namespace Barotrauma.Items.Components
|
||||
private readonly Sprite doorSprite, weldedSprite, brokenSprite;
|
||||
private readonly bool scaleBrokenSprite, fadeBrokenSprite;
|
||||
private readonly bool autoOrientGap;
|
||||
|
||||
private bool isJammed;
|
||||
public bool IsJammed
|
||||
{
|
||||
get { return isJammed; }
|
||||
set
|
||||
{
|
||||
if (isJammed == value) { return; }
|
||||
isJammed = value;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private bool isStuck;
|
||||
public bool IsStuck
|
||||
@@ -297,7 +311,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (toggleCooldownTimer > 0.0f && user != lastUser) { OnFailedToOpen(); return; }
|
||||
toggleCooldownTimer = ToggleCoolDown;
|
||||
if (IsStuck) { toggleCooldownTimer = 1.0f; OnFailedToOpen(); return; }
|
||||
if (IsStuck || IsJammed) { toggleCooldownTimer = 1.0f; OnFailedToOpen(); return; }
|
||||
lastUser = user;
|
||||
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true, forcedOpen: actionType == ActionType.OnPicked);
|
||||
}
|
||||
@@ -341,7 +355,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
bool isClosing = false;
|
||||
if (!IsStuck)
|
||||
if ((!IsStuck && !IsJammed) || !isOpen)
|
||||
{
|
||||
if (PredictedState == null)
|
||||
{
|
||||
@@ -630,7 +644,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
if (IsStuck) { return; }
|
||||
if (IsStuck || IsJammed) { return; }
|
||||
|
||||
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void DischargeProjSpecific();
|
||||
|
||||
private void FindNodes(Vector2 worldPosition, float range)
|
||||
public void FindNodes(Vector2 worldPosition, float range)
|
||||
{
|
||||
//see which submarines are within range so we can skip structures that are in far-away subs
|
||||
List<Submarine> submarinesInRange = new List<Submarine>();
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
@@ -138,19 +139,25 @@ namespace Barotrauma.Items.Components
|
||||
public TileSide Sides = TileSide.None;
|
||||
public TileSide BlockedSides = TileSide.None;
|
||||
|
||||
public readonly FoliageConfig FlowerConfig;
|
||||
public readonly FoliageConfig LeafConfig;
|
||||
public FoliageConfig FlowerConfig;
|
||||
public FoliageConfig LeafConfig;
|
||||
|
||||
public int FailedGrowthAttempts;
|
||||
public Rectangle Rect;
|
||||
public Vector2 Position;
|
||||
public Color HealthColor = Color.Transparent;
|
||||
public float DecayDelay;
|
||||
|
||||
private float VineStep;
|
||||
private float FlowerStep;
|
||||
private readonly float diameter;
|
||||
public Vector2 offset;
|
||||
|
||||
public VineTileType Type;
|
||||
public readonly Dictionary<TileSide, Vector2> AdjacentPositions;
|
||||
public static int Size = 32;
|
||||
|
||||
|
||||
public float VineStep;
|
||||
public float FlowerStep;
|
||||
|
||||
private float growthStep;
|
||||
|
||||
public float GrowthStep
|
||||
{
|
||||
get => growthStep;
|
||||
@@ -166,17 +173,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private readonly float diameter;
|
||||
private Vector2 offset;
|
||||
public Color HealthColor = Color.Transparent;
|
||||
public float DecayDelay;
|
||||
|
||||
private readonly Growable Parent;
|
||||
public VineTileType Type;
|
||||
private readonly Growable? Parent;
|
||||
|
||||
public readonly Dictionary<TileSide, Vector2> AdjacentPositions;
|
||||
|
||||
public static int Size = 32;
|
||||
|
||||
public VineTile(Growable parent, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
|
||||
public VineTile(Growable? parent, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
|
||||
{
|
||||
FlowerConfig = flowerConfig ?? FoliageConfig.EmptyConfig;
|
||||
LeafConfig = leafConfig ?? FoliageConfig.EmptyConfig;
|
||||
@@ -197,7 +199,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void UpdateScale(float deltaTime)
|
||||
{
|
||||
if (Parent.Decayed && GrowthStep > 1.0f)
|
||||
bool decayed = Parent?.Decayed ?? false;
|
||||
|
||||
if (decayed && GrowthStep > 1.0f)
|
||||
{
|
||||
if (DecayDelay > 0)
|
||||
{
|
||||
@@ -209,7 +213,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (GrowthStep >= 2.0f || Parent.Decayed) { return; }
|
||||
if (GrowthStep >= 2.0f || decayed) { return; }
|
||||
|
||||
GrowthStep += deltaTime;
|
||||
|
||||
@@ -289,6 +293,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool CanGrowMore() => (Sides | BlockedSides).Count() < 4;
|
||||
|
||||
public bool IsSideBlocked(TileSide side) => BlockedSides.IsBitSet(side) || Sides.IsBitSet(side);
|
||||
|
||||
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size);
|
||||
}
|
||||
|
||||
@@ -313,6 +319,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static TileSide GetOppositeSide(this TileSide side)
|
||||
{
|
||||
return (TileSide) (1 << ((int) Math.Log2((int) side) + 2) % 4);
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Growable : ItemComponent, IServerSerializable
|
||||
@@ -705,8 +716,7 @@ namespace Barotrauma.Items.Components
|
||||
// if the X value is bigger than Y it's to the left or right of us and then check if X is negative or positive to determine if it's right or left
|
||||
TileSide connectingSide = absDistX > absDistY ? distX > 0 ? TileSide.Right : TileSide.Left : distY > 0 ? TileSide.Top : TileSide.Bottom;
|
||||
|
||||
// We use log2 to find the index and offset that index by 2 since the opposite side is always 2 offsets away
|
||||
TileSide oppositeSide = (TileSide) (1 << ((int) Math.Log2((int) connectingSide) + 2) % 4);
|
||||
TileSide oppositeSide = connectingSide.GetOppositeSide();
|
||||
|
||||
if (otherVine.BlockedSides.IsBitSet(connectingSide))
|
||||
{
|
||||
@@ -810,9 +820,9 @@ namespace Barotrauma.Items.Components
|
||||
return element;
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
flowerTiles = componentElement.GetAttributeIntArray("flowertiles", new int[0]);
|
||||
Decayed = componentElement.GetAttributeBool("decayed", false);
|
||||
|
||||
|
||||
@@ -195,7 +195,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
characterUsable = element.GetAttributeBool("characterusable", true);
|
||||
}
|
||||
|
||||
private bool OnPusherCollision(Fixture sender, Fixture other, Contact contact)
|
||||
@@ -211,9 +213,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
|
||||
if (usePrefabValues)
|
||||
{
|
||||
@@ -514,9 +516,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!attachable || item.body == null) { return character == null || character.IsKeyDown(InputType.Aim); }
|
||||
if (!attachable || item.body == null) { return character == null || (character.IsKeyDown(InputType.Aim) && characterUsable); }
|
||||
if (character != null)
|
||||
{
|
||||
if (!characterUsable && !attachable) { return false; }
|
||||
if (!character.IsKeyDown(InputType.Aim)) { return false; }
|
||||
if (!CanBeAttached(character)) { return false; }
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class IdCard : Pickable
|
||||
{
|
||||
public IdCard(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Initialize(CharacterInfo info)
|
||||
{
|
||||
if (info == null) return;
|
||||
|
||||
if (info.Job?.Prefab != null)
|
||||
{
|
||||
item.AddTag("jobid:" + info.Job.Prefab.Identifier);
|
||||
}
|
||||
|
||||
var head = info.Head;
|
||||
|
||||
if (info != null && head != null)
|
||||
{
|
||||
item.AddTag("gender:" + head.gender.ToString().ToLowerInvariant());
|
||||
item.AddTag("race:" + head.race.ToString());
|
||||
item.AddTag("headspriteid:" + info.HeadSpriteId.ToString());
|
||||
item.AddTag("hairindex:" + head.HairIndex);
|
||||
item.AddTag("beardindex:" + head.BeardIndex);
|
||||
item.AddTag("moustacheindex:" + head.MoustacheIndex);
|
||||
item.AddTag("faceattachmentindex:" + head.FaceAttachmentIndex);
|
||||
|
||||
if (head.SheetIndex != null)
|
||||
{
|
||||
item.AddTag("sheetindex:" + head.SheetIndex.Value.X + ";" + head.SheetIndex.Value.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
base.Equip(character);
|
||||
character.Info.CheckDisguiseStatus(true, this);
|
||||
}
|
||||
|
||||
public override void Unequip(Character character)
|
||||
{
|
||||
base.Unequip(character);
|
||||
character.Info.CheckDisguiseStatus(true, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (holdable == null) { return; }
|
||||
|
||||
deattachTimer = Math.Max(0.0f, value);
|
||||
#if SERVER
|
||||
if (deattachTimer >= DeattachDuration)
|
||||
@@ -57,7 +60,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool Attached
|
||||
{
|
||||
get { return holdable == null ? false : holdable.Attached; }
|
||||
get { return holdable != null && holdable.Attached; }
|
||||
}
|
||||
|
||||
public LevelResource(Item item, XElement element) : base(item, element)
|
||||
@@ -67,7 +70,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!holdable.Attached)
|
||||
if (holdable != null && !holdable.Attached)
|
||||
{
|
||||
trigger.Enabled = false;
|
||||
IsActive = false;
|
||||
@@ -87,7 +90,6 @@ namespace Barotrauma.Items.Components
|
||||
holdable = item.GetComponent<Holdable>();
|
||||
if (holdable == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while initializing item \"" + item.Name + "\". Level resources require a Holdable component.");
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
+13
-47
@@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -128,54 +129,19 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
|
||||
if (projectile == null) { return true; }
|
||||
|
||||
float spread = GetSpread(character);
|
||||
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
|
||||
rotation += spread * Rand.Range(-0.5f, 0.5f);
|
||||
|
||||
projectile.User = character;
|
||||
//add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
projectile.IgnoredBodies = new List<Body>(limbBodies);
|
||||
|
||||
Vector2 projectilePos = item.SimPosition;
|
||||
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
if (projectile != null)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = barrelPos;
|
||||
}
|
||||
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
|
||||
{
|
||||
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
|
||||
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
|
||||
}
|
||||
|
||||
projectile.Item.body.ResetDynamics();
|
||||
projectile.Item.SetTransform(projectilePos, rotation);
|
||||
|
||||
projectile.Use(deltaTime);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(item, projectile.Item);
|
||||
if (projectile.Item.Removed) { continue; }
|
||||
projectile.User = character;
|
||||
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
projectile.Item.SetTransform(projectilePos,
|
||||
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
|
||||
|
||||
item.RemoveContained(projectile.Item);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
//recoil
|
||||
item.body.ApplyLinearImpulse(
|
||||
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
|
||||
float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f);
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
|
||||
if (i == 0)
|
||||
{
|
||||
Item.body.ApplyLinearImpulse(new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * Item.body.Mass * -50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
Item.RemoveContained(projectile.Item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -53,6 +54,18 @@ namespace Barotrauma.Items.Components
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much damage is applied to ballast flora.")]
|
||||
public float FireDamage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How many units of damage the item removes from destructible level walls per second.")]
|
||||
public float LevelWallFixAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much the item decreases the size of fires per second.")]
|
||||
public float ExtinguishAmount
|
||||
{
|
||||
@@ -183,23 +196,40 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Vector2 rayStart;
|
||||
Vector2 rayStartWorld;
|
||||
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
|
||||
Vector2 barrelPos = item.SimPosition + ConvertUnits.ToSimUnits(TransformedBarrelPos);
|
||||
//make sure there's no obstacles between the base of the item (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(sourcePos, barrelPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
{
|
||||
//no obstacles -> we start the raycast at the end of the barrel
|
||||
rayStart = ConvertUnits.ToSimUnits(item.WorldPosition + TransformedBarrelPos);
|
||||
rayStart = ConvertUnits.ToSimUnits(item.Position + TransformedBarrelPos);
|
||||
rayStartWorld = ConvertUnits.ToSimUnits(item.WorldPosition + TransformedBarrelPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
rayStart = Submarine.LastPickedPosition + Submarine.LastPickedNormal * 0.1f;
|
||||
if (item.Submarine != null) { rayStart += item.Submarine.SimPosition; }
|
||||
rayStart = rayStartWorld = Submarine.LastPickedPosition + Submarine.LastPickedNormal * 0.1f;
|
||||
if (item.Submarine != null) { rayStartWorld += item.Submarine.SimPosition; }
|
||||
}
|
||||
|
||||
//if the calculated barrel pos is in another hull, use the origin of the item to make sure the particles don't end up in an incorrect hull
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
var barrelHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(rayStartWorld), item.CurrentHull, useWorldCoordinates: true);
|
||||
if (barrelHull != null && barrelHull != item.CurrentHull)
|
||||
{
|
||||
if (MathUtils.GetLineRectangleIntersection(ConvertUnits.ToDisplayUnits(sourcePos), ConvertUnits.ToDisplayUnits(rayStart), item.CurrentHull.Rect, out Vector2 hullIntersection))
|
||||
{
|
||||
Vector2 rayDir = rayStart.NearlyEquals(sourcePos) ? Vector2.Zero : Vector2.Normalize(rayStart - sourcePos);
|
||||
rayStartWorld = ConvertUnits.ToSimUnits(hullIntersection - rayDir * 5.0f);
|
||||
if (item.Submarine != null) { rayStartWorld += item.Submarine.SimPosition; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float spread = MathHelper.ToRadians(MathHelper.Lerp(UnskilledSpread, Spread, degreeOfSuccess));
|
||||
float angle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + spread * Rand.Range(-0.5f, 0.5f);
|
||||
Vector2 rayEnd = rayStart +
|
||||
Vector2 rayEnd = rayStartWorld +
|
||||
ConvertUnits.ToSimUnits(new Vector2(
|
||||
(float)Math.Cos(angle),
|
||||
(float)Math.Sin(angle)) * Range * item.body.Dir);
|
||||
@@ -218,7 +248,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
activeTimer = 0.1f;
|
||||
|
||||
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
|
||||
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStartWorld);
|
||||
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
|
||||
|
||||
Submarine parentSub = character?.Submarine ?? item.Submarine;
|
||||
@@ -232,16 +262,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Repair(rayStart - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
Repair(rayStartWorld - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
}
|
||||
Repair(rayStart, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
Repair(rayStartWorld, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
}
|
||||
else
|
||||
{
|
||||
Repair(rayStart - parentSub.SimPosition, rayEnd - parentSub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
Repair(rayStartWorld - parentSub.SimPosition, rayEnd - parentSub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
}
|
||||
|
||||
UseProjSpecific(deltaTime, rayStart);
|
||||
UseProjSpecific(deltaTime, rayStartWorld);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -289,6 +319,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure || (f.Body?.UserData is Item it && it.GetComponent<Planter>() != null)) { return false; }
|
||||
if (f.Body?.UserData as string == "ruinroom") { return false; }
|
||||
if (f.Body?.UserData is VineTile && !(FireDamage > 0)) { return false; }
|
||||
return true;
|
||||
},
|
||||
allowInsideFixture: true);
|
||||
@@ -324,9 +355,16 @@ namespace Barotrauma.Items.Components
|
||||
hitCharacters.Add(hitCharacter);
|
||||
}
|
||||
|
||||
//if repairing through walls is not allowed and the next wall is more than 100 pixels away from the previous one, stop here
|
||||
//(= repairing multiple overlapping walls is allowed as long as the edges of the walls are less than 100 pixels apart)
|
||||
float thisBodyFraction = Submarine.LastPickedBodyDist(body);
|
||||
if (!RepairThroughWalls && lastHitType == typeof(Structure) && Range * (thisBodyFraction - lastPickedFraction) > 100.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (FixBody(user, deltaTime, degreeOfSuccess, body))
|
||||
{
|
||||
lastPickedFraction = Submarine.LastPickedBodyDist(body);
|
||||
lastPickedFraction = thisBodyFraction;
|
||||
if (bodyType != null) { lastHitType = bodyType; }
|
||||
}
|
||||
}
|
||||
@@ -341,6 +379,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure) { return false; }
|
||||
if (f.Body?.UserData as string == "ruinroom") { return false; }
|
||||
if (f.Body?.UserData is VineTile && !(FireDamage > 0)) { return false; }
|
||||
|
||||
if (f.Body?.UserData is Item targetItem)
|
||||
{
|
||||
if (!HitItems) { return false; }
|
||||
@@ -479,6 +519,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is Voronoi2.VoronoiCell cell)
|
||||
{
|
||||
var levelWall = Level.Loaded?.ExtraWalls.Find(w => w.Body == cell.Body) as DestructibleLevelWall;
|
||||
if (levelWall != null)
|
||||
{
|
||||
levelWall.AddDamage(-LevelWallFixAmount * deltaTime, item.WorldPosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter.Removed) { return false; }
|
||||
@@ -569,6 +618,13 @@ namespace Barotrauma.Items.Components
|
||||
FixItemProjSpecific(user, deltaTime, targetItem);
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is BallastFloraBranch branch)
|
||||
{
|
||||
if (branch.ParentBallastFlora is { } ballastFlora)
|
||||
{
|
||||
ballastFlora.DamageBranch(branch, FireDamage * deltaTime, BallastFloraBehavior.AttackType.Fire, user);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -769,7 +825,8 @@ namespace Barotrauma.Items.Components
|
||||
object value = property.GetValue(target);
|
||||
if (door.Stuck > 0)
|
||||
{
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White, "progressbar.welding");
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White,
|
||||
effect.propertyEffects[i].GetType() == typeof(float) && (float)effect.propertyEffects[i] < 0 ? "progressbar.cutting" : "progressbar.welding");
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +63,14 @@ namespace Barotrauma.Items.Components
|
||||
if (item.body.LinearVelocity.LengthSquared() < 0.01f)
|
||||
{
|
||||
CurrentThrower = null;
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnImpact))
|
||||
if (statusEffectLists?.ContainsKey(ActionType.OnImpact) ?? false)
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
|
||||
{
|
||||
statusEffect.SetUser(null);
|
||||
}
|
||||
}
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnBroken))
|
||||
if (statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false)
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
|
||||
{
|
||||
@@ -135,14 +135,14 @@ namespace Barotrauma.Items.Components
|
||||
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
CurrentThrower = picker;
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnImpact))
|
||||
if (statusEffectLists?.ContainsKey(ActionType.OnImpact) ?? false)
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnImpact])
|
||||
{
|
||||
statusEffect.SetUser(CurrentThrower);
|
||||
}
|
||||
}
|
||||
if (statusEffectLists.ContainsKey(ActionType.OnBroken))
|
||||
if (statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false)
|
||||
{
|
||||
foreach (var statusEffect in statusEffectLists[ActionType.OnBroken])
|
||||
{
|
||||
|
||||
@@ -763,7 +763,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Load(XElement componentElement, bool usePrefabValues)
|
||||
public virtual void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
if (componentElement != null)
|
||||
{
|
||||
@@ -1011,6 +1011,7 @@ namespace Barotrauma.Items.Components
|
||||
if (FindSuitableContainer(character,
|
||||
i =>
|
||||
{
|
||||
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (container.Inventory.IsFull()) { return 0; }
|
||||
|
||||
@@ -415,17 +415,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
|
||||
string containedString = componentElement.GetAttributeString("contained", "");
|
||||
string[] itemIdStrings = containedString.Split(',');
|
||||
itemIds = new ushort[itemIdStrings.Length];
|
||||
for (int i = 0; i < itemIdStrings.Length; i++)
|
||||
{
|
||||
if (!ushort.TryParse(itemIdStrings[i], out ushort id)) { continue; }
|
||||
itemIds[i] = id;
|
||||
if (!int.TryParse(itemIdStrings[i], out int id)) { continue; }
|
||||
itemIds[i] = idRemap.GetOffsetId(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -318,16 +318,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
|
||||
{
|
||||
Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y);
|
||||
Vector2 centerPos = new Vector2(focusTarget.WorldRect.Center.X, focusTarget.WorldRect.Center.Y);
|
||||
|
||||
Item targetItem = focusTarget as Item;
|
||||
if (targetItem != null)
|
||||
Turret turret = focusTarget.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
Turret turret = targetItem.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
centerPos = new Vector2(targetItem.WorldRect.X + turret.TransformedBarrelPos.X, targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
centerPos = new Vector2(focusTarget.WorldRect.X + turret.TransformedBarrelPos.X, focusTarget.WorldRect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
|
||||
Vector2 offset = character.CursorWorldPosition - centerPos;
|
||||
@@ -356,6 +352,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
#if CLIENT
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return false; }
|
||||
#endif
|
||||
if (IsToggle)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Barotrauma.Items.Components
|
||||
forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100));
|
||||
}
|
||||
|
||||
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage / MinVoltage, 1.0f);
|
||||
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
|
||||
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
|
||||
//less effective when in a bad condition
|
||||
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
|
||||
@@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components
|
||||
UpdatePropellerDamage(deltaTime);
|
||||
float maxChangeSpeed = 0.5f;
|
||||
float modifier = 2;
|
||||
float noise = currForce.Length() * forceMultiplier * modifier / maxForce;
|
||||
float noise = MathUtils.NearlyEqual(0.0f, maxForce) ? 0.0f : currForce.Length() * forceMultiplier * modifier / maxForce;
|
||||
float min = Math.Max(1 - maxChangeSpeed, 0);
|
||||
float max = 1 + maxChangeSpeed;
|
||||
UpdateAITargets(Math.Clamp(noise, min, max), deltaTime);
|
||||
|
||||
@@ -482,9 +482,9 @@ namespace Barotrauma.Items.Components
|
||||
return componentElement;
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
savedFabricatedItem = componentElement.GetAttributeString("fabricateditemidentifier", "");
|
||||
savedTimeUntilReady = componentElement.GetAttributeFloat("savedtimeuntilready", 0.0f);
|
||||
savedRequiredTime = componentElement.GetAttributeFloat("savedrequiredtime", 0.0f);
|
||||
|
||||
+17
-23
@@ -8,11 +8,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class OxygenGenerator : Powered
|
||||
{
|
||||
private float powerDownTimer;
|
||||
|
||||
private float generatedAmount;
|
||||
|
||||
private List<Vent> ventList;
|
||||
//key = vent, float = total volume of the hull the vent is in and the hulls connected to it
|
||||
private Dictionary<Vent, float> ventList;
|
||||
|
||||
private float totalHullVolume;
|
||||
|
||||
@@ -49,17 +48,12 @@ namespace Barotrauma.Items.Components
|
||||
Voltage = 1.0f;
|
||||
}
|
||||
|
||||
if (item.CurrentHull == null) return;
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
if (Voltage < MinVoltage)
|
||||
{
|
||||
powerDownTimer += deltaTime;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
powerDownTimer = 0.0f;
|
||||
}
|
||||
|
||||
CurrFlow = Math.Min(Voltage, 1.0f) * generatedAmount * 100.0f;
|
||||
|
||||
@@ -76,24 +70,25 @@ namespace Barotrauma.Items.Components
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
powerDownTimer += deltaTime;
|
||||
CurrFlow = 0.0f;
|
||||
}
|
||||
|
||||
private void GetVents()
|
||||
{
|
||||
ventList.Clear();
|
||||
|
||||
ventList = new Dictionary<Vent, float>();
|
||||
foreach (MapEntity entity in item.linkedTo)
|
||||
{
|
||||
Item linkedItem = entity as Item;
|
||||
if (linkedItem == null) continue;
|
||||
if (!(entity is Item linkedItem)) { continue; }
|
||||
|
||||
Vent vent = linkedItem.GetComponent<Vent>();
|
||||
if (vent == null) continue;
|
||||
if (vent?.Item.CurrentHull == null) { continue; }
|
||||
|
||||
ventList.Add(vent);
|
||||
if (linkedItem.CurrentHull != null) totalHullVolume += linkedItem.CurrentHull.Volume;
|
||||
ventList.Add(vent, 0.0f);
|
||||
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: true, searchDepth: 10, ignoreClosedGaps: true))
|
||||
{
|
||||
totalHullVolume += connectedHull.Volume;
|
||||
ventList[vent] += connectedHull.Volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,18 +96,17 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (ventList == null)
|
||||
{
|
||||
ventList = new List<Vent>();
|
||||
GetVents();
|
||||
}
|
||||
|
||||
if (!ventList.Any() || totalHullVolume <= 0.0f) return;
|
||||
if (!ventList.Any() || totalHullVolume <= 0.0f) { return; }
|
||||
|
||||
foreach (Vent v in ventList)
|
||||
foreach (KeyValuePair<Vent, float> v in ventList)
|
||||
{
|
||||
if (v.Item.CurrentHull == null) continue;
|
||||
if (v.Key?.Item.CurrentHull == null) { continue; }
|
||||
|
||||
v.OxygenFlow = deltaOxygen * (v.Item.CurrentHull.Volume / totalHullVolume);
|
||||
v.IsActive = true;
|
||||
v.Key.OxygenFlow = deltaOxygen * (v.Value / totalHullVolume);
|
||||
v.Key.IsActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -11,10 +13,38 @@ namespace Barotrauma.Items.Components
|
||||
private float flowPercentage;
|
||||
private float maxFlow;
|
||||
|
||||
private float? targetLevel;
|
||||
public float? TargetLevel;
|
||||
|
||||
private bool hijacked;
|
||||
public bool Hijacked
|
||||
{
|
||||
get { return hijacked; }
|
||||
set
|
||||
{
|
||||
if (value == hijacked) { return; }
|
||||
hijacked = value;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private float pumpSpeedLockTimer, isActiveLockTimer;
|
||||
|
||||
private bool infected;
|
||||
|
||||
[Serialize(false, true, description: "Whether or not the pump is infected with ballast flora spores.")]
|
||||
public bool Infected
|
||||
{
|
||||
get => infected;
|
||||
set
|
||||
{
|
||||
infected = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string InfectIdentifier;
|
||||
|
||||
[Serialize(0.0f, true, description: "How fast the item is currently pumping water (-100 = full speed out, 100 = full speed in). Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
public float FlowPercentage
|
||||
{
|
||||
@@ -66,12 +96,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
currFlow = 0.0f;
|
||||
|
||||
if (targetLevel != null)
|
||||
if (TargetLevel != null)
|
||||
{
|
||||
pumpSpeedLockTimer -= deltaTime;
|
||||
float hullPercentage = 0.0f;
|
||||
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
|
||||
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
|
||||
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
|
||||
@@ -92,14 +122,41 @@ namespace Barotrauma.Items.Components
|
||||
//less effective when in a bad condition
|
||||
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
|
||||
|
||||
|
||||
if (currFlow < 0 && Infected)
|
||||
{
|
||||
InfectBallast(InfectIdentifier);
|
||||
}
|
||||
Infected = false;
|
||||
|
||||
item.CurrentHull.WaterVolume += currFlow;
|
||||
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
|
||||
}
|
||||
|
||||
public void InfectBallast(string identifier)
|
||||
{
|
||||
Hull hull = item.CurrentHull;
|
||||
if (hull == null) { return; }
|
||||
|
||||
// if the ship is already infected then do nothing
|
||||
if (Hull.hullList.Where(h => h.Submarine == hull.Submarine).Any(h => h.BallastFlora != null)) { return; }
|
||||
|
||||
if (hull.BallastFlora != null) { return; }
|
||||
|
||||
Vector2 offset = item.WorldPosition - hull.WorldPosition;
|
||||
hull.BallastFlora = new BallastFloraBehavior(hull, BallastFloraPrefab.Find(identifier), offset, firstGrowth: true);
|
||||
|
||||
#if SERVER
|
||||
hull.BallastFlora.SendNetworkMessage(hull.BallastFlora, BallastFloraBehavior.NetworkHeader.Spawn);
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
if (Hijacked) { return; }
|
||||
|
||||
if (connection.Name == "toggle")
|
||||
{
|
||||
IsActive = !IsActive;
|
||||
@@ -115,7 +172,7 @@ namespace Barotrauma.Items.Components
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
|
||||
{
|
||||
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
|
||||
targetLevel = null;
|
||||
TargetLevel = null;
|
||||
pumpSpeedLockTimer = 0.1f;
|
||||
}
|
||||
}
|
||||
@@ -123,7 +180,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
|
||||
{
|
||||
targetLevel = MathHelper.Clamp(tempTarget + 50.0f, 0.0f, 100.0f);
|
||||
TargetLevel = MathHelper.Clamp(tempTarget + 50.0f, 0.0f, 100.0f);
|
||||
pumpSpeedLockTimer = 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool useDirectionalPing = false;
|
||||
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
|
||||
private bool useMineralScanner;
|
||||
|
||||
private bool aiPingCheckPending;
|
||||
|
||||
@@ -103,6 +104,9 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, false, description: "Does the sonar have mineral scanning mode?")]
|
||||
public bool HasMineralScanner { get; set; }
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get { return zoom; }
|
||||
@@ -343,6 +347,7 @@ namespace Barotrauma.Items.Components
|
||||
bool isActive = msg.ReadBoolean();
|
||||
bool directionalPing = useDirectionalPing;
|
||||
float zoomT = zoom, pingDirectionT = 0.0f;
|
||||
bool mineralScanner = useMineralScanner;
|
||||
if (isActive)
|
||||
{
|
||||
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
@@ -351,6 +356,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
}
|
||||
mineralScanner = msg.ReadBoolean();
|
||||
}
|
||||
|
||||
if (!item.CanClientAccess(c)) { return; }
|
||||
@@ -366,9 +372,14 @@ namespace Barotrauma.Items.Components
|
||||
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
|
||||
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
|
||||
}
|
||||
useMineralScanner = mineralScanner;
|
||||
#if CLIENT
|
||||
zoomSlider.BarScroll = zoomT;
|
||||
directionalModeSwitch.Selected = useDirectionalPing;
|
||||
if (mineralScannerSwitch != null)
|
||||
{
|
||||
mineralScannerSwitch.Selected = useMineralScanner;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if SERVER
|
||||
@@ -388,6 +399,7 @@ namespace Barotrauma.Items.Components
|
||||
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
|
||||
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
|
||||
}
|
||||
msg.Write(useMineralScanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,6 +417,7 @@ namespace Barotrauma.Items.Components
|
||||
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 0.75f));
|
||||
|
||||
float avoidRadius = avoidDist.Length();
|
||||
float damagingWallAvoidRadius = avoidRadius * 1.5f;
|
||||
|
||||
Vector2 newAvoidStrength = Vector2.Zero;
|
||||
|
||||
@@ -426,12 +427,26 @@ namespace Barotrauma.Items.Components
|
||||
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
if (Level.Loaded?.ExtraWalls.Any(w => w.WallDamageOnTouch > 0.0f && w.Cells.Contains(cell)) ?? false)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition);
|
||||
float dist = Vector2.Distance(closestPoint, controlledSub.WorldPosition);
|
||||
if (dist > damagingWallAvoidRadius) { continue; }
|
||||
Vector2 diff = controlledSub.WorldPosition - cell.Center;
|
||||
Vector2 avoid = Vector2.Normalize(diff) * (damagingWallAvoidRadius - dist) / damagingWallAvoidRadius;
|
||||
newAvoidStrength += avoid;
|
||||
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, edge.Center, 1.0f, avoid, cell.Translation));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
Vector2 diff = controlledSub.WorldPosition - intersection;
|
||||
|
||||
//far enough -> ignore
|
||||
if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
|
||||
{
|
||||
|
||||
@@ -13,11 +13,7 @@ namespace Barotrauma.Items.Components
|
||||
set { oxygenFlow = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
public Vent (Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
}
|
||||
public Vent (Item item, XElement element) : base(item, element) { }
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
|
||||
@@ -55,6 +55,22 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private float extraLoad;
|
||||
private float extraLoadSetTime;
|
||||
/// <summary>
|
||||
/// Additional load coming from somewhere else than the devices connected to the junction box (e.g. ballast flora or piezo crystals).
|
||||
/// Goes back to zero automatically if you stop setting the value.
|
||||
/// </summary>
|
||||
public float ExtraLoad
|
||||
{
|
||||
get { return extraLoad; }
|
||||
set
|
||||
{
|
||||
extraLoad = Math.Max(value, 0.0f);
|
||||
extraLoadSetTime = (float)Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
//can the component transfer power
|
||||
private bool canTransfer;
|
||||
public bool CanTransfer
|
||||
@@ -135,6 +151,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
RefreshConnections();
|
||||
|
||||
if (Timing.TotalTime > extraLoadSetTime + 1.0)
|
||||
{
|
||||
extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
|
||||
}
|
||||
|
||||
if (!CanTransfer) { return; }
|
||||
|
||||
if (isBroken)
|
||||
|
||||
@@ -231,8 +231,16 @@ namespace Barotrauma.Items.Components
|
||||
//and send out a "probe signal" which the PowerTransfer components use to add up the grid power/load
|
||||
foreach (Powered powered in poweredList)
|
||||
{
|
||||
if (powered is PowerTransfer) { continue; }
|
||||
if (powered.currPowerConsumption > 0.0f)
|
||||
if (powered is PowerTransfer pt)
|
||||
{
|
||||
if (pt.ExtraLoad > 0.0f)
|
||||
{
|
||||
lastPowerProbeRecipients.Clear();
|
||||
powered.powerIn?.SendPowerProbeSignal(powered.item, -pt.ExtraLoad);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (powered.currPowerConsumption > 0.0f)
|
||||
{
|
||||
//consuming power
|
||||
lastPowerProbeRecipients.Clear();
|
||||
|
||||
@@ -60,14 +60,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public List<Body> IgnoredBodies;
|
||||
|
||||
private Character user;
|
||||
private Character _user;
|
||||
public Character User
|
||||
{
|
||||
get { return user; }
|
||||
get { return _user; }
|
||||
set
|
||||
{
|
||||
user = value;
|
||||
Attack?.SetUser(user);
|
||||
_user = value;
|
||||
Attack?.SetUser(_user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,51 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
private void Launch(Character user, Vector2 simPosition, float rotation)
|
||||
{
|
||||
//User = user;
|
||||
Item.body.ResetDynamics();
|
||||
Item.SetTransform(simPosition, rotation);
|
||||
Use();
|
||||
if (Item.Removed) { return; }
|
||||
User = user;
|
||||
launchPos = simPosition;
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians));
|
||||
}
|
||||
|
||||
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent)
|
||||
{
|
||||
//add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
IgnoredBodies = ignoredBodies;
|
||||
Vector2 projectilePos = weaponPos;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(weaponPos, spawnPos, IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = spawnPos;
|
||||
}
|
||||
else if ((weaponPos - spawnPos).LengthSquared() > 0.0001f)
|
||||
{
|
||||
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
|
||||
Vector2 newPos = weaponPos - Vector2.Normalize(spawnPos - projectilePos) * Math.Max(Item.body.GetMaxExtent(), 0.1f);
|
||||
if (MathUtils.IsValid(newPos))
|
||||
{
|
||||
projectilePos = newPos;
|
||||
}
|
||||
}
|
||||
Launch(user, projectilePos, rotation);
|
||||
if (createNetworkEvent && !Item.Removed && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
#if SERVER
|
||||
launchRot = rotation;
|
||||
Item.CreateServerEvent(this, new object[] { true }); //true = indicate that this is a launch event
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public bool Use(Character character = null)
|
||||
{
|
||||
if (character != null && !characterUsable) { return false; }
|
||||
|
||||
@@ -230,16 +274,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Launch(launchDir * LaunchImpulse * item.body.Mass);
|
||||
DoLaunch(launchDir * LaunchImpulse * item.body.Mass);
|
||||
}
|
||||
}
|
||||
|
||||
User = character;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Launch(Vector2 impulse)
|
||||
public override bool Use(float deltaTime, Character character = null) => Use(character);
|
||||
|
||||
private void DoLaunch(Vector2 impulse)
|
||||
{
|
||||
hits.Clear();
|
||||
|
||||
@@ -360,6 +404,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return true; }
|
||||
if (fixture.Body.UserData is VineTile) { return true; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
|
||||
if (fixture.Body?.UserData as string == "ruinroom") { return true; }
|
||||
|
||||
@@ -381,6 +426,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
|
||||
if (fixture.Body.UserData is VineTile) { return -1; }
|
||||
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
|
||||
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
|
||||
@@ -577,20 +623,27 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Attack != null) { attackResult = Attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); }
|
||||
}
|
||||
else if (target.Body.UserData is VoronoiCell voronoiCell && Attack != null && Math.Abs(Attack.StructureDamage) > 0.0f)
|
||||
{
|
||||
if (Level.Loaded?.ExtraWalls.Find(w => w.Body == target.Body) is DestructibleLevelWall destructibleWall)
|
||||
{
|
||||
attackResult = Attack.DoDamage(User, destructibleWall, item.WorldPosition, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (character != null) { character.LastDamageSource = item; }
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, user: user);
|
||||
PlaySound(ActionType.OnImpact, user: user);
|
||||
PlaySound(ActionType.OnUse, user: _user);
|
||||
PlaySound(ActionType.OnImpact, user: _user);
|
||||
#endif
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (target.Body.UserData is Limb targetLimb)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: user);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: _user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: _user);
|
||||
var attack = targetLimb.attack;
|
||||
if (attack != null)
|
||||
{
|
||||
@@ -626,8 +679,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
@@ -639,6 +692,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
|
||||
target.Body.LinearVelocity = target.Body.LinearVelocity.ClampLength(NetConfig.MaxPhysicsBodyVelocity * 0.5f);
|
||||
|
||||
if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
|
||||
{
|
||||
@@ -702,7 +756,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (RemoveOnHit)
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -446,7 +446,7 @@ namespace Barotrauma.Items.Components
|
||||
//oxygen generators don't deteriorate if they're not running
|
||||
if (oxyGenerator.CurrFlow > 0.1f) { return true; }
|
||||
}
|
||||
else if (ic is Powered powered)
|
||||
else if (ic is Powered powered && !(powered is LightComponent))
|
||||
{
|
||||
if (powered.Voltage >= powered.MinVoltage) { return true; }
|
||||
}
|
||||
@@ -477,6 +477,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateFixAnimation(Character character)
|
||||
{
|
||||
if (character == null || character.IsDead || character.IsIncapacitated) { return; }
|
||||
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
get
|
||||
{
|
||||
if (recipientsDirty) RefreshRecipients();
|
||||
if (recipientsDirty) { RefreshRecipients(); }
|
||||
return recipients;
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
|
||||
return "Connection (" + item.Name + ", " + Name + ")";
|
||||
}
|
||||
|
||||
public Connection(XElement element, ConnectionPanel connectionPanel)
|
||||
public Connection(XElement element, ConnectionPanel connectionPanel, IdRemap idRemap)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
@@ -150,8 +150,11 @@ namespace Barotrauma.Items.Components
|
||||
if (index == -1) break;
|
||||
|
||||
int id = subElement.GetAttributeInt("w", 0);
|
||||
if (id < 0) id = 0;
|
||||
wireId[index] = (ushort)id;
|
||||
if (id < 0)
|
||||
{
|
||||
id = 0;
|
||||
}
|
||||
wireId[index] = idRemap.GetOffsetId(id);
|
||||
|
||||
break;
|
||||
|
||||
@@ -162,6 +165,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRecipientsDirty()
|
||||
{
|
||||
recipientsDirty = true;
|
||||
}
|
||||
|
||||
private void RefreshRecipients()
|
||||
{
|
||||
recipients.Clear();
|
||||
@@ -304,6 +312,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false;
|
||||
wires[i].Connect(this, false, false);
|
||||
wires[i].FixNodeEnds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
Connections.Add(new Connection(subElement, this));
|
||||
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
|
||||
break;
|
||||
case "output":
|
||||
Connections.Add(new Connection(subElement, this));
|
||||
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -218,9 +218,9 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Load(XElement element, bool usePrefabValues)
|
||||
public override void Load(XElement element, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(element, usePrefabValues);
|
||||
base.Load(element, usePrefabValues, idRemap);
|
||||
|
||||
List<Connection> loadedConnections = new List<Connection>();
|
||||
|
||||
@@ -229,10 +229,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
loadedConnections.Add(new Connection(subElement, this));
|
||||
loadedConnections.Add(new Connection(subElement, this, idRemap));
|
||||
break;
|
||||
case "output":
|
||||
loadedConnections.Add(new Connection(subElement, this));
|
||||
loadedConnections.Add(new Connection(subElement, this, idRemap));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-21
@@ -14,12 +14,12 @@ namespace Barotrauma.Items.Components
|
||||
private Color lightColor;
|
||||
private float lightBrightness;
|
||||
private float blinkFrequency;
|
||||
private float pulseFrequency, pulseAmount;
|
||||
private float range;
|
||||
private float flicker, flickerState;
|
||||
private float flicker, flickerSpeed;
|
||||
private bool castShadows;
|
||||
private bool drawBehindSubs;
|
||||
|
||||
private float blinkTimer;
|
||||
|
||||
private double lastToggleSignalTime;
|
||||
|
||||
@@ -90,14 +90,49 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.Flicker = flicker; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(1.0f, false, description: "How fast the light flickers.")]
|
||||
public float FlickerSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return flickerSpeed; }
|
||||
set
|
||||
{
|
||||
flickerSpeed = value;
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.FlickerSpeed = flickerSpeed; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, true, description: "How rapidly the light pulsates (in Hz). 0 = no blinking.")]
|
||||
public float PulseFrequency
|
||||
{
|
||||
get { return pulseFrequency; }
|
||||
set
|
||||
{
|
||||
pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.PulseFrequency = pulseFrequency; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, true, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
public float PulseAmount
|
||||
{
|
||||
get { return pulseAmount; }
|
||||
set
|
||||
{
|
||||
pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.PulseAmount = pulseAmount; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
|
||||
@@ -107,6 +142,9 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
|
||||
#if CLIENT
|
||||
if (light != null) { light.LightSourceParams.BlinkFrequency = blinkFrequency; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,11 +199,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
ParentSub = item.CurrentHull?.Submarine,
|
||||
Position = item.Position,
|
||||
CastShadows = castShadows,
|
||||
CastShadows = castShadows,
|
||||
IsBackground = drawBehindSubs,
|
||||
SpriteScale = Vector2.One * item.Scale,
|
||||
Range = range
|
||||
};
|
||||
light.LightSourceParams.Flicker = flicker;
|
||||
light.LightSourceParams.FlickerSpeed = FlickerSpeed;
|
||||
light.LightSourceParams.PulseAmount = pulseAmount;
|
||||
light.LightSourceParams.PulseFrequency = pulseFrequency;
|
||||
light.LightSourceParams.BlinkFrequency = blinkFrequency;
|
||||
#endif
|
||||
|
||||
IsActive = IsOn;
|
||||
@@ -229,22 +272,7 @@ namespace Barotrauma.Items.Components
|
||||
lightBrightness = MathHelper.Lerp(lightBrightness, powerConsumption <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f), 0.1f);
|
||||
}
|
||||
|
||||
if (blinkFrequency > 0.0f)
|
||||
{
|
||||
blinkTimer = (blinkTimer + deltaTime * blinkFrequency) % 1.0f;
|
||||
}
|
||||
|
||||
if (blinkTimer > 0.5f)
|
||||
{
|
||||
SetLightSourceState(false, lightBrightness);
|
||||
}
|
||||
else
|
||||
{
|
||||
flickerState += deltaTime * FlickerSpeed;
|
||||
flickerState %= 255;
|
||||
float noise = PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * flicker;
|
||||
SetLightSourceState(true, lightBrightness * (1.0f - noise));
|
||||
}
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
|
||||
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -7,6 +8,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private const int MaxMessageLength = ChatMessage.MaxLength;
|
||||
|
||||
private const int MaxMessages = 60;
|
||||
|
||||
private List<string> messageHistory = new List<string>(MaxMessages);
|
||||
|
||||
public string DisplayedWelcomeMessage
|
||||
{
|
||||
get;
|
||||
@@ -50,5 +55,41 @@ namespace Barotrauma.Items.Components
|
||||
string inputSignal = signal.Replace("\\n", "\n");
|
||||
ShowOnDisplay(inputSignal);
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
|
||||
{
|
||||
ShowOnDisplay(DisplayedWelcomeMessage);
|
||||
DisplayedWelcomeMessage = "";
|
||||
//remove welcome message if a game session is running so it doesn't reappear on successive rounds
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
welcomeMessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
var componentElement = base.Save(parentElement);
|
||||
for (int i = 0; i < messageHistory.Count; i++)
|
||||
{
|
||||
componentElement.Add(new XAttribute("msg" + i, messageHistory[i]));
|
||||
}
|
||||
return componentElement;
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
for (int i = 0; i < MaxMessages; i++)
|
||||
{
|
||||
string msg = componentElement.GetAttributeString("msg" + i, null);
|
||||
if (msg == null) { break; }
|
||||
ShowOnDisplay(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-12
@@ -17,7 +17,8 @@ namespace Barotrauma.Items.Components
|
||||
Atan,
|
||||
}
|
||||
|
||||
protected float[] receivedSignal = new float[2];
|
||||
private float[] receivedSignal = new float[2];
|
||||
private float[] timeSinceReceived = new float[2];
|
||||
|
||||
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
|
||||
public FunctionType Function
|
||||
@@ -41,12 +42,25 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//reset received signals
|
||||
receivedSignal[0] = float.NaN;
|
||||
receivedSignal[1] = float.NaN;
|
||||
if (Function == FunctionType.Atan)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
if (timeSinceReceived[i] > 0.1f)
|
||||
{
|
||||
receivedSignal[i] = float.NaN;
|
||||
}
|
||||
}
|
||||
if (!float.IsNaN(receivedSignal[0]) && !float.IsNaN(receivedSignal[1]))
|
||||
{
|
||||
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
|
||||
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
|
||||
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
|
||||
{
|
||||
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
|
||||
@@ -89,17 +103,13 @@ namespace Barotrauma.Items.Components
|
||||
case FunctionType.Atan:
|
||||
if (connection.Name == "signal_in_x")
|
||||
{
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
|
||||
}
|
||||
else if (connection.Name == "signal_in_y")
|
||||
{
|
||||
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
|
||||
if (!float.IsNaN(receivedSignal[0]) && !float.IsNaN(receivedSignal[1]))
|
||||
{
|
||||
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
|
||||
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
|
||||
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
}
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -59,6 +61,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.SendSignal(0, signalOut, "signal_out", null);
|
||||
}
|
||||
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
int waterPercentage = MathHelper.Clamp((int)Math.Round(item.CurrentHull.WaterPercentage), 0, 100);
|
||||
item.SendSignal(0, waterPercentage.ToString(), "water_%", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool Hidden;
|
||||
|
||||
private float removeNodeDelay;
|
||||
private float editNodeDelay;
|
||||
|
||||
private bool locked;
|
||||
public bool Locked
|
||||
@@ -200,14 +200,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (connections[0] != null && connections[0] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) < Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos))
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (connections[1] != null && connections[1] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) < Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos))
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
@@ -290,7 +292,7 @@ namespace Barotrauma.Items.Components
|
||||
if (nodes.Count == 0) { return; }
|
||||
|
||||
Character user = item.ParentInventory?.Owner as Character;
|
||||
removeNodeDelay = (user?.SelectedConstruction == null) ? removeNodeDelay - deltaTime : 0.5f;
|
||||
editNodeDelay = (user?.SelectedConstruction == null) ? editNodeDelay - deltaTime : 0.5f;
|
||||
|
||||
Submarine sub = item.Submarine;
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
@@ -416,7 +418,9 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
//clients communicate node addition/removal with network events
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { return false; }
|
||||
if (newNodePos != Vector2.Zero && canPlaceNode && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > MinNodeDistance)
|
||||
|
||||
if (newNodePos != Vector2.Zero && canPlaceNode && editNodeDelay <= 0.0f && nodes.Count > 0 &&
|
||||
Vector2.DistanceSquared(newNodePos, nodes[nodes.Count - 1]) > MinNodeDistance * MinNodeDistance)
|
||||
{
|
||||
if (nodes.Count >= MaxNodeCount)
|
||||
{
|
||||
@@ -440,6 +444,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
}
|
||||
editNodeDelay = 0.1f;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -450,7 +455,7 @@ namespace Barotrauma.Items.Components
|
||||
//clients communicate node addition/removal with network events
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { return false; }
|
||||
|
||||
if (nodes.Count > 1 && removeNodeDelay <= 0.0f)
|
||||
if (nodes.Count > 1 && editNodeDelay <= 0.0f)
|
||||
{
|
||||
nodes.RemoveAt(nodes.Count - 1);
|
||||
UpdateSections();
|
||||
@@ -466,7 +471,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
}
|
||||
removeNodeDelay = 0.1f;
|
||||
editNodeDelay = 0.1f;
|
||||
|
||||
Drawable = IsActive || sections.Count > 0;
|
||||
return true;
|
||||
@@ -617,8 +622,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (connections[i]?.Item != null)
|
||||
{
|
||||
var pt = connections[i].Item.GetComponent<PowerTransfer>();
|
||||
if (pt != null) pt.SetConnectionDirty(connections[i]);
|
||||
connections[i].Item.GetComponent<PowerTransfer>()?.SetConnectionDirty(connections[i]);
|
||||
connections[i].SetRecipientsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,17 +653,29 @@ namespace Barotrauma.Items.Components
|
||||
} while (removed);
|
||||
}
|
||||
|
||||
private void FixNodeEnds()
|
||||
public void FixNodeEnds()
|
||||
{
|
||||
if (connections[0] == null || connections[1] == null || nodes.Count == 0) { return; }
|
||||
Item item0 = connections[0]?.Item;
|
||||
Item item1 = connections[1]?.Item;
|
||||
|
||||
if (item0 == null && item1 != null)
|
||||
{
|
||||
item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
||||
}
|
||||
else if (item0 != null && item1 == null)
|
||||
{
|
||||
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
||||
}
|
||||
|
||||
if (item0 == null || item1 == null || nodes.Count == 0) { return; }
|
||||
|
||||
Vector2 nodePos = nodes[0];
|
||||
|
||||
Submarine refSub = connections[0].Item.Submarine ?? connections[1].Item.Submarine;
|
||||
Submarine refSub = item0.Submarine ?? item1.Submarine;
|
||||
if (refSub != null) { nodePos += refSub.HiddenSubPosition; }
|
||||
|
||||
float dist1 = Vector2.DistanceSquared(connections[0].Item.Position, nodePos);
|
||||
float dist2 = Vector2.DistanceSquared(connections[1].Item.Position, nodePos);
|
||||
float dist1 = Vector2.DistanceSquared(item0.Position, nodePos);
|
||||
float dist2 = Vector2.DistanceSquared(item1.Position, nodePos);
|
||||
|
||||
//first node is closer to the second item
|
||||
//= the nodes are "backwards", need to reverse them
|
||||
@@ -747,9 +764,9 @@ namespace Barotrauma.Items.Components
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
|
||||
string nodeString = componentElement.GetAttributeString("nodes", "");
|
||||
if (nodeString == "") return;
|
||||
|
||||
@@ -441,9 +441,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private int loadedVariant = -1;
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
loadedVariant = componentElement.GetAttributeInt("variant", -1);
|
||||
}
|
||||
public override void OnItemLoaded()
|
||||
|
||||
@@ -11,6 +11,8 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
@@ -27,7 +29,7 @@ namespace Barotrauma
|
||||
|
||||
private HashSet<string> tags;
|
||||
|
||||
private bool isWire;
|
||||
private bool isWire, isLogic;
|
||||
|
||||
private Hull currentHull;
|
||||
public Hull CurrentHull
|
||||
@@ -626,6 +628,8 @@ namespace Barotrauma
|
||||
get { return Prefab.Linkable; }
|
||||
}
|
||||
|
||||
public BallastFloraBranch Infector { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -640,14 +644,15 @@ namespace Barotrauma
|
||||
{
|
||||
get { return allPropertyObjects; }
|
||||
}
|
||||
|
||||
|
||||
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine)
|
||||
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: this(new Rectangle(
|
||||
(int)(position.X - itemPrefab.sprite.size.X / 2 * itemPrefab.Scale),
|
||||
(int)(position.Y + itemPrefab.sprite.size.Y / 2 * itemPrefab.Scale),
|
||||
(int)(itemPrefab.sprite.size.X * itemPrefab.Scale),
|
||||
(int)(itemPrefab.sprite.size.Y * itemPrefab.Scale)),
|
||||
itemPrefab, submarine)
|
||||
itemPrefab, submarine, id: id)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -656,8 +661,8 @@ namespace Barotrauma
|
||||
/// Creates a new item
|
||||
/// </summary>
|
||||
/// <param name="callOnItemLoaded">Should the OnItemLoaded methods of the ItemComponents be called. Use false if the item needs additional initialization before it can be considered fully loaded (e.g. when loading an item from a sub file or cloning an item).</param>
|
||||
public Item(Rectangle newRect, ItemPrefab itemPrefab, Submarine submarine, bool callOnItemLoaded = true)
|
||||
: base(itemPrefab, submarine)
|
||||
public Item(Rectangle newRect, ItemPrefab itemPrefab, Submarine submarine, bool callOnItemLoaded = true, ushort id = Entity.NullEntityID)
|
||||
: base(itemPrefab, submarine, id)
|
||||
{
|
||||
spriteColor = prefab.SpriteColor;
|
||||
|
||||
@@ -737,6 +742,8 @@ namespace Barotrauma
|
||||
case "upgrademodule":
|
||||
case "upgradeoverride":
|
||||
case "minimapicon":
|
||||
case "infectedsprite":
|
||||
case "damagedinfectedsprite":
|
||||
break;
|
||||
case "staticbody":
|
||||
StaticBodyConfig = subElement;
|
||||
@@ -834,7 +841,8 @@ namespace Barotrauma
|
||||
|
||||
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
||||
|
||||
if (Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; }
|
||||
if (Components.Any() && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; }
|
||||
if (HasTag("logic")) { isLogic = true; }
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
@@ -1172,6 +1180,23 @@ namespace Barotrauma
|
||||
|
||||
return rootContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the item or any of its containers of the item set to be ignored?
|
||||
/// </summary>
|
||||
public bool IsThisOrAnyContainerIgnoredByAI()
|
||||
{
|
||||
if (IgnoreByAI) { return true; }
|
||||
if (Container == null) { return false; }
|
||||
if (Container.IgnoreByAI) { return true; }
|
||||
var container = Container;
|
||||
while (container.Container != null)
|
||||
{
|
||||
container = container.Container;
|
||||
if (container.IgnoreByAI) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsOwnedBy(Entity entity) => FindParentInventory(i => i.Owner == entity) != null;
|
||||
|
||||
@@ -1419,6 +1444,7 @@ namespace Barotrauma
|
||||
if (!isActive) { return; }
|
||||
|
||||
ApplyStatusEffects(ActionType.Always, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
|
||||
ApplyStatusEffects(parentInventory == null ? ActionType.OnNotContained : ActionType.OnContained, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
|
||||
|
||||
for (int i = 0; i < updateableComponents.Count; i++)
|
||||
{
|
||||
@@ -1451,8 +1477,6 @@ namespace Barotrauma
|
||||
#endif
|
||||
ic.WasUsed = false;
|
||||
|
||||
ic.ApplyStatusEffects(parentInventory == null ? ActionType.OnNotContained : ActionType.OnContained, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
|
||||
|
||||
if (ic.IsActive)
|
||||
{
|
||||
if (condition <= 0.0f)
|
||||
@@ -2481,9 +2505,9 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateNetPosition(float deltaTime);
|
||||
|
||||
public static Item Load(XElement element, Submarine submarine)
|
||||
public static Item Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
return Load(element, submarine, createNetworkEvent: false);
|
||||
return Load(element, submarine, createNetworkEvent: false, idRemap: idRemap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2493,7 +2517,7 @@ namespace Barotrauma
|
||||
/// <param name="submarine">The submarine to spawn the item in (can be null)</param>
|
||||
/// <param name="createNetworkEvent">Should an EntitySpawner event be created to notify clients about the item being created.</param>
|
||||
/// <returns></returns>
|
||||
public static Item Load(XElement element, Submarine submarine, bool createNetworkEvent)
|
||||
public static Item Load(XElement element, Submarine submarine, bool createNetworkEvent, IdRemap idRemap)
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
@@ -2512,13 +2536,11 @@ namespace Barotrauma
|
||||
rect.Height = (int)(prefab.Size.Y * prefab.Scale);
|
||||
}
|
||||
|
||||
Item item = new Item(rect, prefab, submarine, callOnItemLoaded: false)
|
||||
Item item = new Item(rect, prefab, submarine, callOnItemLoaded: false, id: idRemap.GetOffsetId(element))
|
||||
{
|
||||
Submarine = submarine,
|
||||
ID = (ushort)int.Parse(element.Attribute("ID").Value),
|
||||
linkedToID = new List<ushort>()
|
||||
};
|
||||
item.OriginalID = item.ID;
|
||||
|
||||
#if SERVER
|
||||
if (createNetworkEvent)
|
||||
@@ -2543,15 +2565,7 @@ namespace Barotrauma
|
||||
if (shouldBeLoaded) { property.TrySetValue(item, attribute.Value); }
|
||||
}
|
||||
|
||||
string linkedToString = element.GetAttributeString("linked", "");
|
||||
if (linkedToString != "")
|
||||
{
|
||||
string[] linkedToIds = linkedToString.Split(',');
|
||||
for (int i = 0; i < linkedToIds.Length; i++)
|
||||
{
|
||||
item.linkedToID.Add((ushort)int.Parse(linkedToIds[i]));
|
||||
}
|
||||
}
|
||||
item.ParseLinks(element, idRemap);
|
||||
|
||||
bool thisIsOverride = element.GetAttributeBool("isoverride", false);
|
||||
|
||||
@@ -2583,7 +2597,7 @@ namespace Barotrauma
|
||||
{
|
||||
ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString());
|
||||
if (component == null) { continue; }
|
||||
component.Load(subElement, usePrefabValues);
|
||||
component.Load(subElement, usePrefabValues, idRemap);
|
||||
unloadedComponents.Remove(component);
|
||||
break;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user