Unstable 0.17.3.0
This commit is contained in:
@@ -1,185 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CameraTransition
|
||||
{
|
||||
public bool Running
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Camera AssignedCamera;
|
||||
private readonly Alignment? cameraStartPos;
|
||||
private readonly Alignment? cameraEndPos;
|
||||
private readonly float? startZoom;
|
||||
private readonly float? endZoom;
|
||||
|
||||
public readonly float WaitDuration;
|
||||
public readonly float PanDuration;
|
||||
public readonly bool FadeOut;
|
||||
public readonly bool LosFadeIn;
|
||||
|
||||
private readonly CoroutineHandle updateCoroutine;
|
||||
|
||||
private Character prevControlled;
|
||||
|
||||
public bool AllowInterrupt = false;
|
||||
public bool RemoveControlFromCharacter = true;
|
||||
|
||||
public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null)
|
||||
{
|
||||
WaitDuration = waitDuration;
|
||||
PanDuration = panDuration;
|
||||
FadeOut = fadeOut;
|
||||
LosFadeIn = losFadeIn;
|
||||
this.cameraStartPos = cameraStartPos;
|
||||
this.cameraEndPos = cameraEndPos;
|
||||
this.startZoom = startZoom;
|
||||
this.endZoom = endZoom;
|
||||
AssignedCamera = cam;
|
||||
|
||||
if (targetEntity == null) { return; }
|
||||
|
||||
Running = true;
|
||||
CoroutineManager.StopCoroutines("CameraTransition");
|
||||
updateCoroutine = CoroutineManager.StartCoroutine(Update(targetEntity, cam), "CameraTransition");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
CoroutineManager.StopCoroutines(updateCoroutine);
|
||||
Running = false;
|
||||
#if CLIENT
|
||||
if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; }
|
||||
if (prevControlled != null && !prevControlled.Removed)
|
||||
{
|
||||
Character.Controlled = prevControlled;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam)
|
||||
{
|
||||
if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }
|
||||
|
||||
prevControlled = Character.Controlled;
|
||||
if (RemoveControlFromCharacter)
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
#endif
|
||||
Character.Controlled = null;
|
||||
}
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
|
||||
float startZoom = this.startZoom ?? cam.Zoom;
|
||||
float endZoom = this.endZoom ?? 0.5f;
|
||||
Vector2 initialCameraPos = cam.Position;
|
||||
Vector2? initialTargetPos = targetEntity?.WorldPosition;
|
||||
|
||||
float timer = -WaitDuration;
|
||||
|
||||
while (timer < PanDuration)
|
||||
{
|
||||
float clampedTimer = Math.Max(timer, 0f);
|
||||
|
||||
if (Screen.Selected != GameMain.GameScreen)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
#if CLIENT
|
||||
if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; }
|
||||
#endif
|
||||
Running = false;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
//switched control to some other character during the transition -> remove control again
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
prevControlled = Character.Controlled;
|
||||
if (RemoveControlFromCharacter)
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
#endif
|
||||
Character.Controlled = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevControlled != null && prevControlled.Removed)
|
||||
{
|
||||
prevControlled = null;
|
||||
}
|
||||
#if CLIENT
|
||||
if (AllowInterrupt && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
|
||||
{
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
Vector2 minPos = targetEntity.WorldPosition;
|
||||
Vector2 maxPos = targetEntity.WorldPosition;
|
||||
if (targetEntity is Submarine sub)
|
||||
{
|
||||
minPos = new Vector2(sub.WorldPosition.X - sub.Borders.Width / 2, sub.WorldPosition.Y - sub.Borders.Height / 2);
|
||||
maxPos = new Vector2(sub.WorldPosition.X + sub.Borders.Width / 2, sub.WorldPosition.Y + sub.Borders.Height / 2);
|
||||
}
|
||||
|
||||
Vector2 startPos = cameraStartPos.HasValue ?
|
||||
new Vector2(
|
||||
MathHelper.Lerp(minPos.X, maxPos.X, (cameraStartPos.Value.ToVector2().X + 1.0f) / 2.0f),
|
||||
MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraStartPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
|
||||
initialCameraPos;
|
||||
if (!cameraStartPos.HasValue && initialTargetPos.HasValue)
|
||||
{
|
||||
startPos += targetEntity.WorldPosition - initialTargetPos.Value;
|
||||
}
|
||||
Vector2 endPos = cameraEndPos.HasValue ?
|
||||
new Vector2(
|
||||
MathHelper.Lerp(minPos.X, maxPos.X, (cameraEndPos.Value.ToVector2().X + 1.0f) / 2.0f),
|
||||
MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
|
||||
prevControlled?.WorldPosition ?? targetEntity.WorldPosition;
|
||||
|
||||
Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration);
|
||||
cam.Translate(cameraPos - cam.Position);
|
||||
|
||||
#if CLIENT
|
||||
cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, clampedTimer / PanDuration);
|
||||
if (clampedTimer / PanDuration > 0.9f)
|
||||
{
|
||||
if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((clampedTimer / PanDuration) - 0.9f) * 10.0f); }
|
||||
}
|
||||
if (LosFadeIn && clampedTimer / PanDuration > 0.8f)
|
||||
{
|
||||
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f;
|
||||
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
}
|
||||
#endif
|
||||
timer += CoroutineManager.UnscaledDeltaTime;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
Running = false;
|
||||
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
#if CLIENT
|
||||
GUI.ScreenOverlayColor = Color.TransparentBlack;
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
GameMain.LightManager.LosAlpha = 1f;
|
||||
#endif
|
||||
|
||||
if (prevControlled != null && !prevControlled.Removed)
|
||||
{
|
||||
Character.Controlled = prevControlled;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly float updateTargetsInterval = 1;
|
||||
private readonly float updateMemoriesInverval = 1;
|
||||
private readonly float attackLimbResetInterval = 2;
|
||||
private readonly float attackLimbSelectionInterval = 3;
|
||||
// Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target.
|
||||
private const float minPriority = 10;
|
||||
|
||||
@@ -65,10 +65,10 @@ namespace Barotrauma
|
||||
|
||||
private float updateTargetsTimer;
|
||||
private float updateMemoriesTimer;
|
||||
private float attackLimbResetTimer;
|
||||
private float attackLimbSelectionTimer;
|
||||
|
||||
private bool IsAttackRunning => AttackingLimb != null && AttackingLimb.attack.IsRunning;
|
||||
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0 || _previousAttackingLimb != null && _previousAttackingLimb.attack.CoolDownTimer > 0;
|
||||
private bool IsAttackRunning => AttackLimb != null && AttackLimb.attack.IsRunning;
|
||||
private bool IsCoolDownRunning => AttackLimb != null && AttackLimb.attack.CoolDownTimer > 0 || _previousAttackLimb != null && _previousAttackLimb.attack.CoolDownTimer > 0;
|
||||
public float CombatStrength => AIParams.CombatStrength;
|
||||
private float Sight => AIParams.Sight;
|
||||
private float Hearing => AIParams.Hearing;
|
||||
@@ -77,25 +77,25 @@ namespace Barotrauma
|
||||
|
||||
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
|
||||
|
||||
private Limb _attackingLimb;
|
||||
private Limb _previousAttackingLimb;
|
||||
public Limb AttackingLimb
|
||||
private Limb _attackLimb;
|
||||
private Limb _previousAttackLimb;
|
||||
public Limb AttackLimb
|
||||
{
|
||||
get { return _attackingLimb; }
|
||||
get { return _attackLimb; }
|
||||
private set
|
||||
{
|
||||
attackLimbResetTimer = 0;
|
||||
if (_attackingLimb != value)
|
||||
if (_attackLimb != value)
|
||||
{
|
||||
_previousAttackingLimb = _attackingLimb;
|
||||
_previousAttackLimb = _attackLimb;
|
||||
_previousAttackLimb?.AttachedRope?.Snap();
|
||||
}
|
||||
if (_attackingLimb != null && value != _attackingLimb && _attackingLimb.attack.CoolDownTimer > 0)
|
||||
else if (_attackLimb != null && _attackLimb.attack.CoolDownTimer <= 0)
|
||||
{
|
||||
SetAimTimer();
|
||||
_attackLimb.AttachedRope?.Snap();
|
||||
}
|
||||
_attackingLimb = value;
|
||||
_attackLimb = value;
|
||||
attackVector = null;
|
||||
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
|
||||
Reverse = _attackLimb != null && _attackLimb.attack.Reverse;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,8 @@ namespace Barotrauma
|
||||
|
||||
private void ReleaseDragTargets()
|
||||
{
|
||||
if (Character.Inventory != null)
|
||||
AttackLimb?.AttachedRope?.Snap();
|
||||
if (Character.Params.CanInteract && Character.Inventory != null)
|
||||
{
|
||||
Character.HeldItems.ForEach(i => i.GetComponent<Holdable>()?.GetRope()?.Snap());
|
||||
}
|
||||
@@ -600,7 +601,7 @@ namespace Barotrauma
|
||||
UpdatePatrol(deltaTime);
|
||||
break;
|
||||
case AIState.Attack:
|
||||
run = !IsCoolDownRunning || AttackingLimb != null && AttackingLimb.attack.FullSpeedAfterAttack;
|
||||
run = !IsCoolDownRunning || AttackLimb != null && AttackLimb.attack.FullSpeedAfterAttack;
|
||||
UpdateAttack(deltaTime);
|
||||
break;
|
||||
case AIState.Eat:
|
||||
@@ -620,7 +621,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
var attackLimb = AttackingLimb ?? GetAttackLimb(SelectedAiTarget.WorldPosition);
|
||||
var attackLimb = AttackLimb ?? GetAttackLimb(SelectedAiTarget.WorldPosition);
|
||||
if (attackLimb != null && squaredDistance <= Math.Pow(attackLimb.attack.Range, 2))
|
||||
{
|
||||
run = true;
|
||||
@@ -875,7 +876,10 @@ namespace Barotrauma
|
||||
if (followLastTarget)
|
||||
{
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
if (target?.Entity != null && !target.Entity.Removed &&
|
||||
PreviousState == AIState.Attack && Character.CurrentHull == null &&
|
||||
(_previousAttackLimb?.attack == null ||
|
||||
_previousAttackLimb?.attack is Attack previousAttack && (previousAttack.AfterAttack != AIBehaviorAfterAttack.FallBack || previousAttack.CoolDownTimer <= 0)))
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
@@ -1126,31 +1130,42 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
attackLimbSelectionTimer -= deltaTime;
|
||||
if (AttackLimb == null || attackLimbSelectionTimer <= 0)
|
||||
{
|
||||
attackLimbSelectionTimer = attackLimbSelectionInterval * Rand.Range(0.9f, 1.1f);
|
||||
if (!IsAttackRunning && !IsCoolDownRunning)
|
||||
{
|
||||
AttackLimb = GetAttackLimb(attackWorldPos);
|
||||
}
|
||||
}
|
||||
|
||||
bool canAttack = true;
|
||||
bool pursue = false;
|
||||
if (IsCoolDownRunning)
|
||||
if (IsCoolDownRunning && (_previousAttackLimb == null || AttackLimb == null || AttackLimb.attack.CoolDownTimer > 0))
|
||||
{
|
||||
var currentAttackLimb = AttackingLimb ?? _previousAttackingLimb;
|
||||
var currentAttackLimb = AttackLimb ?? _previousAttackLimb;
|
||||
if (currentAttackLimb.attack.CoolDownTimer >= currentAttackLimb.attack.CoolDown + currentAttackLimb.attack.CurrentRandomCoolDown - currentAttackLimb.attack.AfterAttackDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (currentAttackLimb.attack.AfterAttack)
|
||||
AIBehaviorAfterAttack activeBehavior = currentAttackLimb.attack.AfterAttack;
|
||||
switch (activeBehavior)
|
||||
{
|
||||
case AIBehaviorAfterAttack.Pursue:
|
||||
case AIBehaviorAfterAttack.PursueIfCanAttack:
|
||||
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
if (activeBehavior == AIBehaviorAfterAttack.Pursue)
|
||||
{
|
||||
canAttack = false;
|
||||
pursue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, true);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1162,13 +1177,13 @@ namespace Barotrauma
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
canAttack = false;
|
||||
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
if (activeBehavior == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
{
|
||||
// Fall back if cannot attack.
|
||||
UpdateFallBack(attackWorldPos, deltaTime, true);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: true);
|
||||
return;
|
||||
}
|
||||
AttackingLimb = null;
|
||||
AttackLimb = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1177,19 +1192,19 @@ namespace Barotrauma
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
AttackingLimb = newLimb;
|
||||
AttackLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
if (activeBehavior == AIBehaviorAfterAttack.Pursue)
|
||||
{
|
||||
canAttack = false;
|
||||
pursue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, true);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1204,10 +1219,15 @@ namespace Barotrauma
|
||||
break;
|
||||
case AIBehaviorAfterAttack.FallBackUntilCanAttack:
|
||||
case AIBehaviorAfterAttack.FollowThroughUntilCanAttack:
|
||||
case AIBehaviorAfterAttack.ReverseUntilCanAttack:
|
||||
if (activeBehavior == AIBehaviorAfterAttack.ReverseUntilCanAttack)
|
||||
{
|
||||
Reverse = true;
|
||||
}
|
||||
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, activeBehavior == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -1217,7 +1237,7 @@ namespace Barotrauma
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, activeBehavior == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -1227,12 +1247,12 @@ namespace Barotrauma
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
AttackingLimb = newLimb;
|
||||
AttackLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, activeBehavior == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1240,7 +1260,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Cooldown not yet expired -> steer away from the target
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, activeBehavior == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1269,7 +1289,7 @@ namespace Barotrauma
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
AttackingLimb = newLimb;
|
||||
AttackLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1291,7 +1311,12 @@ namespace Barotrauma
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: true);
|
||||
return;
|
||||
case AIBehaviorAfterAttack.FallBack:
|
||||
case AIBehaviorAfterAttack.Reverse:
|
||||
default:
|
||||
if (activeBehavior == AIBehaviorAfterAttack.Reverse)
|
||||
{
|
||||
Reverse = true;
|
||||
}
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
|
||||
return;
|
||||
}
|
||||
@@ -1303,12 +1328,13 @@ namespace Barotrauma
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
if (AttackingLimb == null || !IsValidAttack(AttackingLimb, Character.GetAttackContexts(), SelectedAiTarget?.Entity as IDamageable))
|
||||
if (AttackLimb == null || !IsValidAttack(AttackLimb, Character.GetAttackContexts(), SelectedAiTarget?.Entity))
|
||||
{
|
||||
AttackingLimb = GetAttackLimb(attackWorldPos);
|
||||
AttackLimb = GetAttackLimb(attackWorldPos);
|
||||
}
|
||||
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
|
||||
canAttack = AttackLimb != null && AttackLimb.attack.CoolDownTimer <= 0;
|
||||
}
|
||||
|
||||
if (!AIParams.CanOpenDoors)
|
||||
{
|
||||
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
|
||||
@@ -1347,8 +1373,8 @@ namespace Barotrauma
|
||||
// Target a specific limb instead of the target center position
|
||||
if (wallTarget == null && targetCharacter != null)
|
||||
{
|
||||
var targetLimbType = AttackingLimb.Params.Attack.Attack.TargetLimbType;
|
||||
attackTargetLimb = GetTargetLimb(AttackingLimb, targetCharacter, targetLimbType);
|
||||
var targetLimbType = AttackLimb.Params.Attack.Attack.TargetLimbType;
|
||||
attackTargetLimb = GetTargetLimb(AttackLimb, targetCharacter, targetLimbType);
|
||||
if (attackTargetLimb == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
@@ -1361,7 +1387,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackLimb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
|
||||
if (wallTarget != null && Character.Submarine == null)
|
||||
@@ -1389,23 +1415,23 @@ namespace Barotrauma
|
||||
Vector2 CalculateMargin(Vector2 targetVelocity)
|
||||
{
|
||||
if (targetVelocity == Vector2.Zero) { return Vector2.Zero; }
|
||||
float diff = AttackingLimb.attack.Range - AttackingLimb.attack.DamageRange;
|
||||
if (diff <= 0 || toTarget.LengthSquared() <= MathUtils.Pow2(AttackingLimb.attack.DamageRange)) { return Vector2.Zero; }
|
||||
float diff = AttackLimb.attack.Range - AttackLimb.attack.DamageRange;
|
||||
if (diff <= 0 || toTarget.LengthSquared() <= MathUtils.Pow2(AttackLimb.attack.DamageRange)) { return Vector2.Zero; }
|
||||
float dot = Vector2.Dot(Vector2.Normalize(targetVelocity), Vector2.Normalize(Character.AnimController.Collider.LinearVelocity));
|
||||
if (dot <= 0 || !MathUtils.IsValid(dot)) { return Vector2.Zero; }
|
||||
float distanceOffset = diff * AttackingLimb.attack.Duration;
|
||||
float distanceOffset = diff * AttackLimb.attack.Duration;
|
||||
// Intentionally omit the unit conversion because we use distanceOffset as a multiplier.
|
||||
return targetVelocity * distanceOffset * dot;
|
||||
}
|
||||
|
||||
// Check that we can reach the target
|
||||
distance = toTarget.Length();
|
||||
canAttack = distance < AttackingLimb.attack.Range;
|
||||
canAttack = distance < AttackLimb.attack.Range;
|
||||
|
||||
// Crouch if the target is down (only humanoids), so that we can reach it.
|
||||
if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackingLimb.attack.Range * 2)
|
||||
if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2)
|
||||
{
|
||||
if (Math.Abs(toTarget.Y) > AttackingLimb.attack.Range / 2 && Math.Abs(toTarget.X) <= AttackingLimb.attack.Range)
|
||||
if (Math.Abs(toTarget.Y) > AttackLimb.attack.Range / 2 && Math.Abs(toTarget.X) <= AttackLimb.attack.Range)
|
||||
{
|
||||
humanoidAnimController.Crouching = true;
|
||||
}
|
||||
@@ -1413,14 +1439,14 @@ namespace Barotrauma
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
if (AttackingLimb.attack.Ranged)
|
||||
if (AttackLimb.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 offset = AttackLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
Vector2 forward = VectorExtensions.Forward(AttackLimb.body.TransformedRotation - offset * Character.AnimController.Dir);
|
||||
float angle = VectorExtensions.Angle(forward, toTarget);
|
||||
canAttack = angle < MathHelper.ToRadians(AttackingLimb.attack.RequiredAngle);
|
||||
if (canAttack && AttackingLimb.attack.AvoidFriendlyFire)
|
||||
canAttack = angle < MathHelper.ToRadians(AttackLimb.attack.RequiredAngle);
|
||||
if (canAttack && AttackLimb.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;
|
||||
@@ -1434,11 +1460,11 @@ namespace Barotrauma
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
canAttack = !IsBlocked(attackSimPos) && !IsBlocked(AttackingLimb.SimPosition + forward * ConvertUnits.ToSimUnits(AttackingLimb.attack.Range));
|
||||
canAttack = !IsBlocked(attackSimPos) && !IsBlocked(AttackLimb.SimPosition + forward * ConvertUnits.ToSimUnits(AttackLimb.attack.Range));
|
||||
|
||||
bool IsBlocked(Vector2 targetPosition)
|
||||
{
|
||||
foreach (var body in Submarine.PickBodies(AttackingLimb.SimPosition, targetPosition, myBodies, Physics.CollisionCharacter))
|
||||
foreach (var body in Submarine.PickBodies(AttackLimb.SimPosition, targetPosition, myBodies, Physics.CollisionCharacter))
|
||||
{
|
||||
Character hitTarget = null;
|
||||
if (body.UserData is Character c)
|
||||
@@ -1460,22 +1486,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsAttackRunning && !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.
|
||||
if (attackLimbResetTimer > attackLimbResetInterval)
|
||||
{
|
||||
_attackingLimb = null;
|
||||
attackLimbResetTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackLimbResetTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
Limb steeringLimb = canAttack && !AttackingLimb.attack.Ranged ? AttackingLimb : null;
|
||||
Limb steeringLimb = canAttack && !AttackLimb.attack.Ranged ? AttackLimb : 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.
|
||||
@@ -1490,9 +1502,9 @@ namespace Barotrauma
|
||||
|
||||
var pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
|
||||
if (AttackingLimb != null && AttackingLimb.attack.Retreat)
|
||||
if (AttackLimb != null && AttackLimb.attack.Retreat)
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, false);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1527,7 +1539,7 @@ namespace Barotrauma
|
||||
}
|
||||
// When pursuing, we don't want to pursue too close
|
||||
float max = 300;
|
||||
float margin = AttackingLimb != null ? Math.Min(AttackingLimb.attack.Range * 0.9f, max) : max;
|
||||
float margin = AttackLimb != null ? Math.Min(AttackLimb.attack.Range * 0.9f, max) : max;
|
||||
if (!canAttack || distance > margin)
|
||||
{
|
||||
// Steer towards the target if in the same room and swimming
|
||||
@@ -1558,10 +1570,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AttackingLimb.attack.Ranged)
|
||||
if (AttackLimb.attack.Ranged)
|
||||
{
|
||||
float dir = Character.AnimController.Dir;
|
||||
if (dir > 0 && attackWorldPos.X > AttackingLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackingLimb.WorldPosition.X - margin)
|
||||
if (dir > 0 && attackWorldPos.X > AttackLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackLimb.WorldPosition.X - margin)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
@@ -1658,9 +1670,9 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case CirclePhase.CloseIn:
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
|
||||
if (AttackLimb != null && distance > 0 && distance < AttackLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
|
||||
{
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
strikeTimer = AttackLimb.attack.CoolDown;
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
}
|
||||
else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
@@ -1703,10 +1715,10 @@ namespace Barotrauma
|
||||
// When the offset position is outside of the sub it happens that the creature sometimes reaches the target point,
|
||||
// which makes it continue circling around the point (as supposed)
|
||||
// But when there is some offset and the offset is too near, this is not what we want.
|
||||
if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
if (AttackLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
strikeTimer = AttackLimb.attack.CoolDown;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1741,9 +1753,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * requiredDistMultiplier && IsFacing(margin: MathHelper.Lerp(0.5f, 0.9f, currentAttackIntensity)))
|
||||
if (AttackLimb != null && distance > 0 && distance < AttackLimb.attack.Range * requiredDistMultiplier && IsFacing(margin: MathHelper.Lerp(0.5f, 0.9f, currentAttackIntensity)))
|
||||
{
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
strikeTimer = AttackLimb.attack.CoolDown;
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
}
|
||||
canAttack = false;
|
||||
@@ -1800,7 +1812,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (!canAttack || distance > Math.Min(AttackingLimb.attack.Range * 0.9f, 100))
|
||||
if (!canAttack || distance > Math.Min(AttackLimb.attack.Range * 0.9f, 100))
|
||||
{
|
||||
if (pathSteering != null)
|
||||
{
|
||||
@@ -1811,7 +1823,7 @@ namespace Barotrauma
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
}
|
||||
}
|
||||
else if (AttackingLimb.attack.Ranged)
|
||||
else if (AttackLimb.attack.Ranged)
|
||||
{
|
||||
// Too close
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
|
||||
@@ -1824,18 +1836,18 @@ namespace Barotrauma
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
if (!UpdateLimbAttack(deltaTime, AttackLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
}
|
||||
}
|
||||
else if (IsAttackRunning)
|
||||
{
|
||||
AttackingLimb.attack.ResetAttackTimer();
|
||||
AttackLimb.attack.ResetAttackTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidAttack(Limb attackingLimb, IEnumerable<AttackContext> currentContexts, IDamageable target)
|
||||
private bool IsValidAttack(Limb attackingLimb, IEnumerable<AttackContext> currentContexts, Entity target)
|
||||
{
|
||||
if (attackingLimb == null) { return false; }
|
||||
if (target == null) { return false; }
|
||||
@@ -1854,10 +1866,11 @@ namespace Barotrauma
|
||||
// Check that is approximately facing the target
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : attackingLimb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
if (attack.MinRange > 0 && toTarget.LengthSquared() < MathUtils.Pow2(attack.MinRange)) { return false; }
|
||||
float offset = attackingLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
Vector2 forward = VectorExtensions.Forward(attackingLimb.body.TransformedRotation - offset * Character.AnimController.Dir);
|
||||
float angle = VectorExtensions.Angle(forward, toTarget);
|
||||
if (angle > MathHelper.ToRadians(attack.RequiredAngle)) { return false; }
|
||||
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(forward, toTarget));
|
||||
if (angle > attack.RequiredAngle) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1867,7 +1880,7 @@ namespace Barotrauma
|
||||
private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null)
|
||||
{
|
||||
var currentContexts = Character.GetAttackContexts();
|
||||
IDamageable target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity as IDamageable;
|
||||
Entity target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity;
|
||||
if (target == null) { return null; }
|
||||
Limb selectedLimb = null;
|
||||
float currentPriority = -1;
|
||||
@@ -1901,12 +1914,13 @@ namespace Barotrauma
|
||||
|
||||
float CalculatePriority(Limb limb, Vector2 attackPos)
|
||||
{
|
||||
if (Character.AnimController.SimplePhysicsEnabled) { return 1 + limb.attack.Priority; }
|
||||
float prio = 1 + limb.attack.Priority;
|
||||
if (Character.AnimController.SimplePhysicsEnabled) { return prio; }
|
||||
float dist = Vector2.Distance(limb.WorldPosition, attackPos);
|
||||
// The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it.
|
||||
// We also need a max value that is more than the actual range.
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, limb.attack.Range * 3, dist));
|
||||
return (1 + limb.attack.Priority) * distanceFactor;
|
||||
return prio * distanceFactor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1919,7 +1933,7 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
|
||||
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
|
||||
if (Character.Params.CanInteract && attackResult.Damage > 10)
|
||||
if (attackResult.Damage >= AIParams.DamageThreshold)
|
||||
{
|
||||
ReleaseDragTargets();
|
||||
}
|
||||
@@ -2007,9 +2021,11 @@ namespace Barotrauma
|
||||
|
||||
if (State == AIState.Attack && (IsAttackRunning || IsCoolDownRunning))
|
||||
{
|
||||
// Don't retaliate or escape while performing an attack/under cooldown
|
||||
retaliate = false;
|
||||
avoidGunFire = false;
|
||||
if (IsAttackRunning)
|
||||
{
|
||||
avoidGunFire = false;
|
||||
}
|
||||
}
|
||||
if (retaliate)
|
||||
{
|
||||
@@ -2022,7 +2038,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (avoidGunFire)
|
||||
else if (avoidGunFire && attackResult.Damage >= AIParams.DamageThreshold)
|
||||
{
|
||||
State = AIState.Escape;
|
||||
avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f);
|
||||
@@ -2114,7 +2130,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (attackingLimb.attack.CoolDownTimer > 0)
|
||||
{
|
||||
SetAimTimer();
|
||||
SetAimTimer(Math.Min(attackingLimb.attack.CoolDown, 1.5f));
|
||||
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
|
||||
float greed = AIParams.AggressionGreed;
|
||||
if (!(damageTarget is Character))
|
||||
@@ -2245,19 +2261,19 @@ namespace Barotrauma
|
||||
// TODO: test adding some random variance here?
|
||||
attackVector = attackWorldPos - WorldPosition;
|
||||
}
|
||||
Vector2 attackDir = Vector2.Normalize(followThrough ? attackVector.Value : -attackVector.Value);
|
||||
if (!MathUtils.IsValid(attackDir))
|
||||
Vector2 dir = Vector2.Normalize(followThrough ? attackVector.Value : -attackVector.Value);
|
||||
if (!MathUtils.IsValid(dir))
|
||||
{
|
||||
attackDir = Vector2.UnitY;
|
||||
dir = Vector2.UnitY;
|
||||
}
|
||||
steeringManager.SteeringManual(deltaTime, attackDir);
|
||||
if (Character.AnimController.InWater)
|
||||
steeringManager.SteeringManual(deltaTime, dir);
|
||||
if (Character.AnimController.InWater && !Reverse)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
if (checkBlocking)
|
||||
{
|
||||
return !IsBlocked(deltaTime, SimPosition + attackDir * (avoidLookAheadDistance / 2));
|
||||
return !IsBlocked(deltaTime, SimPosition + dir * (avoidLookAheadDistance / 2));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2990,7 +3006,7 @@ namespace Barotrauma
|
||||
if (HasValidPath(requireNonDirty: true)) { return; }
|
||||
wallHits.Clear();
|
||||
Structure wall = null;
|
||||
Vector2 rayStart = AttackingLimb != null ? AttackingLimb.SimPosition : SimPosition;
|
||||
Vector2 rayStart = AttackLimb != null ? AttackLimb.SimPosition : SimPosition;
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
|
||||
{
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
@@ -3303,6 +3319,7 @@ namespace Barotrauma
|
||||
foreach (var triggerObject in activeTriggers)
|
||||
{
|
||||
AITrigger trigger = triggerObject.Key;
|
||||
if (trigger.IsPermanent) { continue; }
|
||||
trigger.UpdateTimer(deltaTime);
|
||||
if (!trigger.IsActive)
|
||||
{
|
||||
@@ -3471,7 +3488,7 @@ namespace Barotrauma
|
||||
disableTailCoroutine = null;
|
||||
}
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
AttackingLimb = null;
|
||||
AttackLimb = null;
|
||||
movementMargin = 0;
|
||||
ResetEscape();
|
||||
if (isStateChanged && to == AIState.Idle && from != to)
|
||||
|
||||
@@ -148,14 +148,14 @@ namespace Barotrauma
|
||||
}
|
||||
if (TargetCharacter != null)
|
||||
{
|
||||
if (enemyAI.AttackingLimb?.attack == null)
|
||||
if (enemyAI.AttackLimb?.attack == null)
|
||||
{
|
||||
DeattachFromBody(reset: true, cooldown: 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
float range = enemyAI.AttackingLimb.attack.DamageRange * 2f;
|
||||
if (Vector2.DistanceSquared(TargetCharacter.WorldPosition, enemyAI.AttackingLimb.WorldPosition) > range * range)
|
||||
float range = enemyAI.AttackLimb.attack.DamageRange * 2f;
|
||||
if (Vector2.DistanceSquared(TargetCharacter.WorldPosition, enemyAI.AttackLimb.WorldPosition) > range * range)
|
||||
{
|
||||
DeattachFromBody(reset: true, cooldown: 1);
|
||||
}
|
||||
@@ -265,11 +265,11 @@ namespace Barotrauma
|
||||
if (enemyAI.IsSteeringThroughGap) { break; }
|
||||
if (_attachPos == Vector2.Zero) { break; }
|
||||
if (!AttachToSub && !AttachToCharacters) { break; }
|
||||
if (enemyAI.AttackingLimb == null) { break; }
|
||||
if (enemyAI.AttackLimb == null) { break; }
|
||||
if (targetBody == null) { break; }
|
||||
if (IsAttached && AttachJoints[0].BodyB == targetBody) { break; }
|
||||
Vector2 referencePos = TargetCharacter != null ? TargetCharacter.WorldPosition : ConvertUnits.ToDisplayUnits(transformedAttachPos);
|
||||
if (Vector2.DistanceSquared(referencePos, enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
||||
if (Vector2.DistanceSquared(referencePos, enemyAI.AttackLimb.WorldPosition) < enemyAI.AttackLimb.attack.DamageRange * enemyAI.AttackLimb.attack.DamageRange)
|
||||
{
|
||||
AttachToBody(transformedAttachPos);
|
||||
}
|
||||
|
||||
@@ -99,8 +99,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (InWater || !CanWalk)
|
||||
{
|
||||
float avg = (SwimSlowParams.MovementSpeed + SwimFastParams.MovementSpeed) / 2.0f;
|
||||
return TargetMovement.LengthSquared() > avg * avg;
|
||||
return TargetMovement.LengthSquared() > SwimSlowParams.MovementSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+8
-11
@@ -448,21 +448,18 @@ namespace Barotrauma
|
||||
movement = TargetMovement;
|
||||
bool isMoving = movement.LengthSquared() > 0.00001f;
|
||||
var mainLimb = MainLimb;
|
||||
if (isMoving)
|
||||
float t = 0.5f;
|
||||
if (isMoving && !SimplePhysicsEnabled && CurrentSwimParams.RotateTowardsMovement)
|
||||
{
|
||||
float t = 0.5f;
|
||||
if (!SimplePhysicsEnabled && CurrentSwimParams.RotateTowardsMovement)
|
||||
Vector2 forward = VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2);
|
||||
float dot = Vector2.Dot(forward, Vector2.Normalize(movement));
|
||||
if (dot < 0)
|
||||
{
|
||||
Vector2 forward = VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2);
|
||||
float dot = Vector2.Dot(forward, Vector2.Normalize(movement));
|
||||
if (dot < 0)
|
||||
{
|
||||
// Reduce the linear movement speed when not facing the movement direction
|
||||
t = MathHelper.Clamp((1 + dot) / 10, 0.01f, 0.1f);
|
||||
}
|
||||
// Reduce the linear movement speed when not facing the movement direction
|
||||
t = MathHelper.Clamp((1 + dot) / 10, 0.01f, 0.1f);
|
||||
}
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, t);
|
||||
}
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, t);
|
||||
//limbs are disabled when simple physics is enabled, no need to move them
|
||||
if (SimplePhysicsEnabled) { return; }
|
||||
mainLimb.PullJointEnabled = true;
|
||||
|
||||
+52
-22
@@ -443,8 +443,6 @@ namespace Barotrauma
|
||||
if (CurrentGroundedParams == null) { return; }
|
||||
Vector2 handPos;
|
||||
|
||||
//if you're allergic to magic numbers, stop reading now
|
||||
|
||||
Limb leftFoot = GetLimb(LimbType.LeftFoot);
|
||||
Limb rightFoot = GetLimb(LimbType.RightFoot);
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
@@ -599,16 +597,20 @@ namespace Barotrauma
|
||||
{
|
||||
float torsoAngle = TorsoAngle.Value;
|
||||
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
|
||||
if (Crouching && !movingHorizontally) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
|
||||
if (Crouching && !movingHorizontally && !aiming) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
|
||||
torsoAngle -= herpesStrength / 150.0f;
|
||||
torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque);
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
if (!aiming && CurrentGroundedParams.FixedHeadAngle && HeadAngle.HasValue)
|
||||
{
|
||||
float headAngle = HeadAngle.Value;
|
||||
if (Crouching && !movingHorizontally) { headAngle -= HumanCrouchParams.ExtraHeadAngleWhenStationary; }
|
||||
head.body.SmoothRotate(headAngle * Dir, CurrentGroundedParams.HeadTorque);
|
||||
}
|
||||
else
|
||||
{
|
||||
RotateHead(head);
|
||||
}
|
||||
|
||||
if (!onGround)
|
||||
{
|
||||
@@ -883,23 +885,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
float targetSpeed = TargetMovement.Length();
|
||||
if (targetSpeed > 0.1f)
|
||||
if (aiming)
|
||||
{
|
||||
if (!aiming)
|
||||
{
|
||||
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
|
||||
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 diff = (mousePos - torso.SimPosition) * Dir;
|
||||
float newRotation = MathUtils.VectorToAngle(diff);
|
||||
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
else
|
||||
else if (targetSpeed > 0.1f)
|
||||
{
|
||||
if (aiming)
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 diff = (mousePos - torso.SimPosition) * Dir;
|
||||
float newRotation = MathUtils.VectorToAngle(diff);
|
||||
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
|
||||
Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
|
||||
torso.body.MoveToPos(Collider.SimPosition + new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f, 5.0f);
|
||||
@@ -914,13 +910,14 @@ namespace Barotrauma
|
||||
{
|
||||
torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.TorsoTorque);
|
||||
}
|
||||
if (HeadAngle.HasValue)
|
||||
|
||||
if (!aiming && CurrentSwimParams.FixedHeadAngle && HeadAngle.HasValue)
|
||||
{
|
||||
head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.HeadTorque);
|
||||
}
|
||||
else
|
||||
{
|
||||
head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.HeadTorque);
|
||||
RotateHead(head);
|
||||
}
|
||||
|
||||
//dont try to move upwards if head is already out of water
|
||||
@@ -951,7 +948,18 @@ namespace Barotrauma
|
||||
|
||||
if (isNotRemote)
|
||||
{
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, movementLerp);
|
||||
float t = movementLerp;
|
||||
if (targetSpeed > 0.00001f && !SimplePhysicsEnabled)
|
||||
{
|
||||
Vector2 forward = VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2);
|
||||
float dot = Vector2.Dot(forward, Vector2.Normalize(movement));
|
||||
if (dot < 0)
|
||||
{
|
||||
// Reduce the linear movement speed when not facing the movement direction
|
||||
t = MathHelper.Clamp((1 + dot) / 10, 0.01f, 0.1f);
|
||||
}
|
||||
}
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, t);
|
||||
}
|
||||
|
||||
WalkPos += movement.Length();
|
||||
@@ -1227,7 +1235,11 @@ namespace Barotrauma
|
||||
|
||||
//apply forces to the collider to move the Character up/down
|
||||
Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass);
|
||||
if (!aiming)
|
||||
if (aiming)
|
||||
{
|
||||
RotateHead(head);
|
||||
}
|
||||
else
|
||||
{
|
||||
float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
|
||||
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque);
|
||||
@@ -1710,6 +1722,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void RotateHead(Limb head)
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 dir = (mousePos - head.SimPosition) * Dir;
|
||||
float rot = MathUtils.VectorToAngle(dir);
|
||||
var neckJoint = GetJointBetweenLimbs(LimbType.Head, LimbType.Torso);
|
||||
if (neckJoint != null)
|
||||
{
|
||||
float offset = MathUtils.WrapAnglePi(GetLimb(LimbType.Torso).body.Rotation);
|
||||
float lowerLimit = neckJoint.LowerLimit + offset;
|
||||
float upperLimit = neckJoint.UpperLimit + offset;
|
||||
float min = Math.Min(lowerLimit, upperLimit);
|
||||
float max = Math.Max(lowerLimit, upperLimit);
|
||||
rot = Math.Clamp(rot, min, max);
|
||||
}
|
||||
head.body.SmoothRotate(rot, CurrentAnimationParams.HeadTorque);
|
||||
}
|
||||
|
||||
private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle)
|
||||
{
|
||||
if (!MathUtils.IsValid(pos))
|
||||
|
||||
@@ -805,7 +805,7 @@ namespace Barotrauma
|
||||
SeverLimbJointProjSpecific(limbJoint, playSound: true);
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.StatusEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.CharacterStatusEventData());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ namespace Barotrauma
|
||||
Pursue,
|
||||
FollowThrough,
|
||||
FollowThroughUntilCanAttack,
|
||||
IdleUntilCanAttack
|
||||
IdleUntilCanAttack,
|
||||
Reverse,
|
||||
ReverseUntilCanAttack
|
||||
}
|
||||
|
||||
struct AttackResult
|
||||
@@ -102,7 +104,7 @@ namespace Barotrauma
|
||||
public bool Retreat { get; private set; }
|
||||
|
||||
private float _range;
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
|
||||
public float Range
|
||||
{
|
||||
get => _range * RangeMultiplier;
|
||||
@@ -110,13 +112,16 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private float _damageRange;
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The min distance from the attack limb to the target to do damage. In distance-based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The min distance from the attack limb to the target to do damage. In distance-based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
|
||||
public float DamageRange
|
||||
{
|
||||
get => _damageRange * RangeMultiplier;
|
||||
set => _damageRange = value;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: ""), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
|
||||
public float MinRange { get; private set; }
|
||||
|
||||
[Serialize(0.25f, IsPropertySaveable.Yes, description: "An approximation of the attack duration. Effectively defines the time window in which the hit can be registered. If set to too low value, it's possible that the attack won't hit the target in time."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, DecimalCount = 2)]
|
||||
public float Duration { get; private set; }
|
||||
|
||||
@@ -686,7 +691,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsValidTarget(AttackTarget targetType) => TargetType == AttackTarget.Any || TargetType == targetType;
|
||||
|
||||
public bool IsValidTarget(IDamageable target)
|
||||
public bool IsValidTarget(Entity target)
|
||||
{
|
||||
return TargetType switch
|
||||
{
|
||||
|
||||
@@ -1885,7 +1885,7 @@ namespace Barotrauma
|
||||
if (!attack.IsValidContext(currentContexts)) { return false; }
|
||||
if (attackTarget != null)
|
||||
{
|
||||
if (!attack.IsValidTarget(attackTarget)) { return false; }
|
||||
if (!attack.IsValidTarget(attackTarget as Entity)) { return false; }
|
||||
if (attackTarget is ISerializableEntity se && attackTarget is Character)
|
||||
{
|
||||
if (attack.Conditionals.Any(c => !c.TargetSelf && !c.Matches(se))) { return false; }
|
||||
@@ -3148,7 +3148,8 @@ namespace Barotrauma
|
||||
|
||||
var itemContainer = item?.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { return; }
|
||||
foreach (Item inventoryItem in Inventory.AllItemsMod)
|
||||
List<Item> inventoryItems = new List<Item>(Inventory.AllItemsMod);
|
||||
foreach (Item inventoryItem in inventoryItems)
|
||||
{
|
||||
if (!itemContainer.Inventory.TryPutItem(inventoryItem, user: null, createNetworkEvent: createNetworkEvents))
|
||||
{
|
||||
@@ -3156,17 +3157,25 @@ namespace Barotrauma
|
||||
inventoryItem.Drop(dropper: this, createNetworkEvent: createNetworkEvents);
|
||||
}
|
||||
}
|
||||
//this needs to happen after the items have been dropped (we can no longer sync dropping the items if the character has been removed)
|
||||
Spawner.AddEntityToRemoveQueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
Spawner.AddEntityToRemoveQueue(this);
|
||||
else
|
||||
{
|
||||
Spawner.AddEntityToRemoveQueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void DespawnNow(bool createNetworkEvents = true)
|
||||
{
|
||||
despawnTimer = GameSettings.CurrentConfig.CorpseDespawnDelay;
|
||||
UpdateDespawn(1.0f, ignoreThresholds: true, createNetworkEvents: createNetworkEvents);
|
||||
Spawner.Update(createNetworkEvents);
|
||||
//update twice: first to spawn the duffel bag and move the items into it, then to remove the character
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Spawner.Update(createNetworkEvents);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveByPrefab(CharacterPrefab prefab)
|
||||
@@ -4012,7 +4021,7 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new StatusEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData());
|
||||
}
|
||||
|
||||
isDead = true;
|
||||
@@ -4168,6 +4177,11 @@ namespace Barotrauma
|
||||
}
|
||||
DebugConsole.Log("Removing character " + Name + " (ID: " + ID + ")");
|
||||
|
||||
#if CLIENT
|
||||
//ensure we apply any pending inventory updates to drop any items that need to be dropped when the character despawns
|
||||
Inventory?.ApplyReceivedState();
|
||||
#endif
|
||||
|
||||
base.Remove();
|
||||
|
||||
foreach (Item heldItem in HeldItems.ToList())
|
||||
@@ -4179,12 +4193,12 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.CrewManager?.KillCharacter(this, resetCrewListIndex: false);
|
||||
|
||||
if (Controlled == this) { Controlled = null; }
|
||||
#endif
|
||||
|
||||
CharacterList.Remove(this);
|
||||
|
||||
if (Controlled == this) { Controlled = null; }
|
||||
|
||||
if (Inventory != null)
|
||||
{
|
||||
foreach (Item item in Inventory.AllItems)
|
||||
@@ -4266,7 +4280,7 @@ namespace Barotrauma
|
||||
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition) &&
|
||||
GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(newItem, new StatusEventData());
|
||||
newItem.CreateStatusEvent();
|
||||
}
|
||||
#if SERVER
|
||||
newItem.GetComponent<Terminal>()?.SyncHistory();
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public struct StatusEventData : IEventData
|
||||
public struct CharacterStatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
}
|
||||
|
||||
@@ -852,6 +852,19 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
public void RecreateHead(MultiplayerPreferences characterSettings)
|
||||
{
|
||||
if (characterSettings.HairIndex == -1 &&
|
||||
characterSettings.BeardIndex == -1 &&
|
||||
characterSettings.MoustacheIndex == -1 &&
|
||||
characterSettings.FaceAttachmentIndex == -1)
|
||||
{
|
||||
//randomize if nothing is set
|
||||
SetAttachments(Rand.RandSync.Unsynced);
|
||||
characterSettings.HairIndex = Head.HairIndex;
|
||||
characterSettings.BeardIndex = Head.BeardIndex;
|
||||
characterSettings.MoustacheIndex = Head.MoustacheIndex;
|
||||
characterSettings.FaceAttachmentIndex = Head.FaceAttachmentIndex;
|
||||
}
|
||||
|
||||
RecreateHead(
|
||||
characterSettings.TagSet.ToImmutableHashSet(),
|
||||
characterSettings.HairIndex,
|
||||
@@ -859,9 +872,14 @@ namespace Barotrauma
|
||||
characterSettings.MoustacheIndex,
|
||||
characterSettings.FaceAttachmentIndex);
|
||||
|
||||
Head.SkinColor = characterSettings.SkinColor;
|
||||
Head.HairColor = characterSettings.HairColor;
|
||||
Head.FacialHairColor = characterSettings.FacialHairColor;
|
||||
Head.SkinColor = ChooseColor(SkinColors, characterSettings.SkinColor);
|
||||
Head.HairColor = ChooseColor(HairColors, characterSettings.HairColor);
|
||||
Head.FacialHairColor = ChooseColor(FacialHairColors, characterSettings.FacialHairColor);
|
||||
|
||||
Color ChooseColor(in ImmutableArray<(Color Color, float Commonness)> availableColors, Color chosenColor)
|
||||
{
|
||||
return availableColors.Any(c => c.Color == chosenColor) ? chosenColor : SelectRandomColor(availableColors, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -52,17 +52,19 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in character health config (" + characterHealth.Character.Name + ") - define vitality multipliers using affliction identifiers or types instead of names.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", "");
|
||||
Identifier afflictionType = subElement.GetAttributeIdentifier("type", "");
|
||||
float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
if (!afflictionIdentifier.IsEmpty)
|
||||
var vitalityMultipliers = subElement.GetAttributeIdentifierArray("identifier", null) ?? subElement.GetAttributeIdentifierArray("identifiers", null);
|
||||
if (vitalityMultipliers == null)
|
||||
{
|
||||
VitalityMultipliers.Add(afflictionIdentifier, multiplier);
|
||||
vitalityMultipliers = subElement.GetAttributeIdentifierArray("type", null) ?? subElement.GetAttributeIdentifierArray("types", null);
|
||||
}
|
||||
if (vitalityMultipliers != null)
|
||||
{
|
||||
float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
vitalityMultipliers.ForEach(i => VitalityMultipliers.Add(i, multiplier));
|
||||
}
|
||||
else
|
||||
{
|
||||
VitalityTypeMultipliers.Add(afflictionType, multiplier);
|
||||
DebugConsole.ThrowError($"Error in character health config {characterHealth.Character.Name}: affliction identifier(s) or type(s) not defined in the \"VitalityMultiplier\" elements!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -540,6 +540,8 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public Items.Components.Rope AttachedRope { get; set; }
|
||||
|
||||
public string Name => Params.Name;
|
||||
|
||||
// These properties are exposed for status effects
|
||||
|
||||
+9
-1
@@ -103,6 +103,9 @@ namespace Barotrauma
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
public float HandMoveStrength { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Is the head angle fixed or does the angle follow the mouse position?"), Editable]
|
||||
public bool FixedHeadAngle { get; set; }
|
||||
}
|
||||
|
||||
abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation
|
||||
@@ -131,7 +134,7 @@ namespace Barotrauma
|
||||
get => MathHelper.ToDegrees(FootAngleInRadians);
|
||||
set
|
||||
{
|
||||
FootAngleInRadians = MathHelper.ToRadians(value);
|
||||
FootAngleInRadians = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
public float FootAngleInRadians { get; private set; }
|
||||
@@ -156,6 +159,9 @@ namespace Barotrauma
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
public float HandMoveStrength { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Is the head angle fixed or does the angle follow the mouse position?"), Editable]
|
||||
public bool FixedHeadAngle { get; set; }
|
||||
}
|
||||
|
||||
public interface IHumanAnimation
|
||||
@@ -166,5 +172,7 @@ namespace Barotrauma
|
||||
float ArmMoveStrength { get; set; }
|
||||
|
||||
float HandMoveStrength { get; set; }
|
||||
|
||||
bool FixedHeadAngle { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ namespace Barotrauma
|
||||
[Serialize(10f, IsPropertySaveable.Yes, "How frequent the recurring idle and attack sounds are?"), Editable(MinValueFloat = 1f, MaxValueFloat = 100f)]
|
||||
public float SoundInterval { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool DrawLast { get; set; }
|
||||
|
||||
public readonly CharacterFile File;
|
||||
|
||||
public XDocument VariantFile { get; private set; }
|
||||
@@ -574,6 +577,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable]
|
||||
public bool AvoidGunfire { get; private set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "How much damage is required for single attack to trigger avoiding/releasing targets."), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float DamageThreshold { get; private set; }
|
||||
|
||||
[Serialize(3f, IsPropertySaveable.Yes, description: "How long the creature avoids gunfire. Also used when the creature is unlatched."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
public float AvoidTime { get; private set; }
|
||||
|
||||
|
||||
+9
-1
@@ -30,6 +30,7 @@ namespace Barotrauma
|
||||
public readonly bool NotSyncedInMultiplayer;
|
||||
public readonly ImmutableHashSet<Type>? AlternativeTypes;
|
||||
public readonly ImmutableHashSet<Identifier> Names;
|
||||
private readonly MethodInfo? contentPathMutator;
|
||||
|
||||
public TypeInfo(Type type)
|
||||
{
|
||||
@@ -40,9 +41,11 @@ namespace Barotrauma
|
||||
var notSyncedInMultiplayerAttribute = type.GetCustomAttribute<NotSyncedInMultiplayer>();
|
||||
NotSyncedInMultiplayer = notSyncedInMultiplayerAttribute != null;
|
||||
AlternativeTypes = reqByCoreAttribute?.AlternativeTypes;
|
||||
contentPathMutator
|
||||
= Type.GetMethod(nameof(MutateContentPath), BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
HashSet<Identifier> names = new HashSet<Identifier> { type.Name.RemoveFromEnd("File").ToIdentifier() };
|
||||
if (type.GetCustomAttribute<AlternativeContentTypeNames>()?.Names is { } altNames)
|
||||
if (type.GetCustomAttribute<AlternativeContentTypeNames>(inherit: false)?.Names is { } altNames)
|
||||
{
|
||||
names.UnionWith(altNames);
|
||||
}
|
||||
@@ -50,6 +53,10 @@ namespace Barotrauma
|
||||
Names = names.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public ContentPath MutateContentPath(ContentPath path)
|
||||
=> (ContentPath?)contentPathMutator?.Invoke(null, new object[] { path })
|
||||
?? path;
|
||||
|
||||
public ContentFile? CreateInstance(ContentPackage contentPackage, ContentPath path) =>
|
||||
(ContentFile?)Activator.CreateInstance(Type, contentPackage, path);
|
||||
}
|
||||
@@ -80,6 +87,7 @@ namespace Barotrauma
|
||||
}
|
||||
try
|
||||
{
|
||||
filePath = type.MutateContentPath(filePath);
|
||||
if (!File.Exists(filePath.FullPath))
|
||||
{
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": file not found.");
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Barotrauma.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class ServerExecutableFile : OtherFile
|
||||
{
|
||||
//This content type doesn't do very much on its own, it's handled manually by the Host Server menu
|
||||
public ServerExecutableFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public static ContentPath MutateContentPath(ContentPath path)
|
||||
{
|
||||
if (File.Exists(path.FullPath)) { return path; }
|
||||
|
||||
string rawValueWithoutExtension()
|
||||
=> Barotrauma.IO.Path.Combine(
|
||||
Barotrauma.IO.Path.GetDirectoryName(path.RawValue ?? ""),
|
||||
Barotrauma.IO.Path.GetFileNameWithoutExtension(path.RawValue ?? "")).CleanUpPath();
|
||||
|
||||
path = ContentPath.FromRaw(path.ContentPackage, rawValueWithoutExtension());
|
||||
if (File.Exists(path.FullPath)) { return path; }
|
||||
|
||||
path = ContentPath.FromRaw(path.ContentPackage,
|
||||
rawValueWithoutExtension() + ".exe");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -110,7 +110,7 @@ namespace Barotrauma
|
||||
!expectedHash.IsNullOrWhiteSpace() &&
|
||||
!expectedHash.Equals(Hash.StringRepresentation, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public IEnumerable<T> GetFiles<T>() where T : ContentFile => Files.Where(f => f is T).Cast<T>();
|
||||
public IEnumerable<T> GetFiles<T>() where T : ContentFile => Files.OfType<T>();
|
||||
|
||||
public IEnumerable<ContentFile> GetFiles(Type type)
|
||||
=> !type.IsSubclassOf(typeof(ContentFile))
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class RequiredByCorePackage : Attribute
|
||||
{
|
||||
public readonly ImmutableHashSet<Type> AlternativeTypes;
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class AlternativeContentTypeNames : Attribute
|
||||
{
|
||||
public readonly ImmutableHashSet<Identifier> Names;
|
||||
|
||||
@@ -327,7 +327,8 @@ namespace Barotrauma
|
||||
=> LocalPackages.Regular.CollectionConcat(WorkshopPackages.Regular);
|
||||
|
||||
public static IEnumerable<ContentPackage> AllPackages
|
||||
=> LocalPackages.CollectionConcat(WorkshopPackages);
|
||||
=> VanillaCorePackage.ToEnumerable().CollectionConcat(LocalPackages).CollectionConcat(WorkshopPackages)
|
||||
.OfType<ContentPackage>();
|
||||
|
||||
public static void UpdateContentPackageList()
|
||||
{
|
||||
|
||||
@@ -168,25 +168,34 @@ namespace Barotrauma
|
||||
};
|
||||
}));
|
||||
|
||||
void printMapEntityPrefabs<T>(IEnumerable<T> prefabs) where T : MapEntityPrefab
|
||||
{
|
||||
NewMessage("***************", Color.Cyan);
|
||||
foreach (T prefab in prefabs)
|
||||
{
|
||||
if (prefab.Name.IsNullOrEmpty()) { continue; }
|
||||
string text = $"- {prefab.Name}";
|
||||
if (prefab.Tags.Any())
|
||||
{
|
||||
text += $" ({string.Join(", ", prefab.Tags)})";
|
||||
}
|
||||
if (prefab.AllowedLinks?.Any() ?? false)
|
||||
{
|
||||
text += $", Links: {string.Join(", ", prefab.AllowedLinks)}";
|
||||
}
|
||||
NewMessage(text, prefab.ContentPackage == ContentPackageManager.VanillaCorePackage ? Color.Cyan : Color.Purple);
|
||||
}
|
||||
NewMessage("***************", Color.Cyan);
|
||||
}
|
||||
|
||||
commands.Add(new Command("items|itemlist", "itemlist: List all the item prefabs available for spawning.", (string[] args) =>
|
||||
{
|
||||
NewMessage("***************", Color.Cyan);
|
||||
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
if (itemPrefab.Name.IsNullOrEmpty()) { continue; }
|
||||
string text = $"- {itemPrefab.Name}";
|
||||
if (itemPrefab.Tags.Any())
|
||||
{
|
||||
text += $" ({string.Join(", ", itemPrefab.Tags)})";
|
||||
}
|
||||
if (itemPrefab.AllowedLinks.Any())
|
||||
{
|
||||
text += $", Links: {string.Join(", ", itemPrefab.AllowedLinks)}";
|
||||
}
|
||||
NewMessage(text, Color.Cyan);
|
||||
}
|
||||
NewMessage("***************", Color.Cyan);
|
||||
printMapEntityPrefabs(ItemPrefab.Prefabs);
|
||||
}));
|
||||
|
||||
commands.Add(new Command("itemassemblies", "itemassemblies: List all the item assemblies available for spawning.", (string[] args) =>
|
||||
{
|
||||
printMapEntityPrefabs(ItemAssemblyPrefab.Prefabs);
|
||||
}));
|
||||
|
||||
|
||||
@@ -202,6 +211,7 @@ namespace Barotrauma
|
||||
string[] creatureAndJobNames =
|
||||
CharacterPrefab.Prefabs.Select(p => p.Identifier.Value)
|
||||
.Concat(JobPrefab.Prefabs.Select(p => p.Identifier.Value))
|
||||
.OrderBy(s => s)
|
||||
.ToArray();
|
||||
|
||||
return new string[][]
|
||||
@@ -732,9 +742,16 @@ namespace Barotrauma
|
||||
{
|
||||
#if CLIENT
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return; }
|
||||
Character.Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
GameMain.Client?.SendConsoleCommand("freecam");
|
||||
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
Character.Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Client?.SendConsoleCommand("freecam");
|
||||
}
|
||||
#endif
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -2382,7 +2399,7 @@ namespace Barotrauma
|
||||
|
||||
public static void ThrowError(LocalizedString error, Exception e = null, bool createMessageBox = false, bool appendStackTrace = false)
|
||||
{
|
||||
ThrowError(error.Value);
|
||||
ThrowError(error.Value, e, createMessageBox, appendStackTrace);
|
||||
}
|
||||
|
||||
public static void ThrowError(string error, Exception e = null, bool createMessageBox = false, bool appendStackTrace = false)
|
||||
|
||||
@@ -153,6 +153,13 @@ namespace Barotrauma
|
||||
|
||||
swarmSpawned = true;
|
||||
}
|
||||
#if DEBUG || UNSTABLE
|
||||
if (State == 1 && !level.CheckBeaconActive())
|
||||
{
|
||||
DebugConsole.ThrowError("Beacon became inactive!");
|
||||
State = 2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void End()
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
int deliveredItemCount = items.Count(i => i.CurrentHull != null && !i.Removed && i.Condition > 0.0f);
|
||||
int deliveredItemCount = items.Count(it => IsItemDelivered(it));
|
||||
if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount)
|
||||
{
|
||||
GiveReward();
|
||||
@@ -267,5 +267,12 @@ namespace Barotrauma
|
||||
items.Clear();
|
||||
failed = !completed;
|
||||
}
|
||||
|
||||
private bool IsItemDelivered(Item item)
|
||||
{
|
||||
if (item.Removed || item.Condition <= 0.0f || Submarine.MainSub == null) { return false; }
|
||||
var submarine = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
return submarine == Submarine.MainSub || Submarine.MainSub.GetConnectedSubs().Contains(submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,17 +419,17 @@ namespace Barotrauma
|
||||
public static int DistributeRewardsToCrew(IEnumerable<Character> crew, int totalReward)
|
||||
{
|
||||
int remainingRewards = totalReward;
|
||||
HashSet<Character> nonBotCrew = crew.Where(c => !c.IsBot).ToHashSet();
|
||||
float sum = nonBotCrew.Sum(c => c.Wallet.RewardDistribution);
|
||||
if (sum == 0) { return remainingRewards; }
|
||||
foreach (Character character in nonBotCrew)
|
||||
float sum = GetRewardDistibutionSum(crew);
|
||||
if (MathUtils.NearlyEqual(sum, 0)) { return remainingRewards; }
|
||||
foreach (Character character in crew)
|
||||
{
|
||||
float rewardWeight = character.Wallet.RewardDistribution / sum;
|
||||
int reward = (int)Math.Floor(totalReward * rewardWeight);
|
||||
reward = Math.Max(remainingRewards, reward);
|
||||
int rewardDistribution = character.Wallet.RewardDistribution;
|
||||
float rewardWeight = sum > 100 ? rewardDistribution / sum : rewardDistribution / 100f;
|
||||
int reward = (int)(totalReward * rewardWeight);
|
||||
reward = Math.Min(remainingRewards, reward);
|
||||
character.Wallet.Give(reward);
|
||||
remainingRewards -= reward;
|
||||
if (0 >= remainingRewards) { break; }
|
||||
if (remainingRewards <= 0) { break; }
|
||||
}
|
||||
|
||||
return remainingRewards;
|
||||
@@ -442,26 +442,27 @@ namespace Barotrauma
|
||||
|
||||
IEnumerable<Character> characters = crewManager.GetCharacters();
|
||||
#if SERVER
|
||||
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(IsAlive).Concat(characters);
|
||||
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead).Concat(characters);
|
||||
#elif CLIENT
|
||||
return characters;
|
||||
#endif
|
||||
static bool IsAlive(Character c) { return c?.Info != null && !c.IsDead; }
|
||||
}
|
||||
|
||||
public static int GetRewardDistibutionSum(IEnumerable<Character> crew, int rewardDistribution = 0) => crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;
|
||||
|
||||
public static (int Amount, int Percentage) GetRewardShare(int rewardDistribution, IEnumerable<Character> crew, Option<int> reward)
|
||||
|
||||
public static (int Amount, int Percentage, float Sum) GetRewardShare(int rewardDistribution, IEnumerable<Character> crew, Option<int> reward)
|
||||
{
|
||||
float sum = crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;
|
||||
if (sum == 0) { return (0, 0); }
|
||||
float sum = GetRewardDistibutionSum(crew, rewardDistribution);
|
||||
if (MathUtils.NearlyEqual(sum, 0)) { return (0, 0, sum); }
|
||||
|
||||
float rewardWeight = rewardDistribution / sum;
|
||||
float rewardWeight = sum > 100 ? rewardDistribution / sum : rewardDistribution / 100f;
|
||||
int rewardPercentage = (int)(rewardWeight * 100);
|
||||
|
||||
return reward switch
|
||||
{
|
||||
Some<int> { Value: var amount } => ((int)(amount * rewardWeight), rewardPercentage),
|
||||
None<int> _ => (0, rewardPercentage),
|
||||
Some<int> { Value: var amount } => ((int)(amount * rewardWeight), rewardPercentage, sum),
|
||||
None<int> _ => (0, rewardPercentage, sum),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace Barotrauma
|
||||
}
|
||||
GiveReward();
|
||||
completed = true;
|
||||
if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase) || t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)))
|
||||
if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
level.LevelData.HasHuntingGrounds = false;
|
||||
}
|
||||
|
||||
@@ -30,22 +30,16 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer = null)
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity)
|
||||
: this(itemPrefab, quantity, buyer: null) { }
|
||||
#endif
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? Character.Controlled?.Info?.ID ?? 0;
|
||||
}
|
||||
#elif SERVER
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, Client buyer)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,18 @@ namespace Barotrauma
|
||||
public int Balance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network message for the server to update wallet values to clients
|
||||
/// </summary>
|
||||
internal struct NetWalletUpdate : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize(ArrayMaxSize = NetConfig.MaxPlayers + 1)]
|
||||
public NetWalletTransaction[] Transactions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network message for the client to transfer money between wallets
|
||||
/// </summary>
|
||||
[NetworkSerialize]
|
||||
internal struct NetWalletTransfer : INetSerializableStruct
|
||||
{
|
||||
@@ -39,7 +45,10 @@ namespace Barotrauma
|
||||
public int Amount;
|
||||
}
|
||||
|
||||
internal struct NetWalletSalaryUpdate : INetSerializableStruct
|
||||
/// <summary>
|
||||
/// Network message for the client to set the salary of someone
|
||||
/// </summary>
|
||||
internal struct NetWalletSetSalaryUpdate : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public ushort Target;
|
||||
@@ -48,6 +57,10 @@ namespace Barotrauma
|
||||
public int NewRewardDistribution;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the difference in balance and salary when a wallet gets updated
|
||||
/// Not really used right now but could be used for notifications when receiving funds similar to how talents do it
|
||||
/// </summary>
|
||||
[NetworkSerialize]
|
||||
internal struct WalletChangedData : INetSerializableStruct
|
||||
{
|
||||
@@ -82,6 +95,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an update that changed the amount of money or salary of the wallet
|
||||
/// </summary>
|
||||
[NetworkSerialize]
|
||||
internal struct NetWalletTransaction : INetSerializableStruct
|
||||
{
|
||||
|
||||
@@ -321,15 +321,32 @@ namespace Barotrauma
|
||||
}
|
||||
if (levelData.HasHuntingGrounds)
|
||||
{
|
||||
var huntingGroundsMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)));
|
||||
var huntingGroundsMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase)));
|
||||
if (!huntingGroundsMissionPrefabs.Any())
|
||||
{
|
||||
DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggroundsnoreward\" found.");
|
||||
DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggrounds\" found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||
var huntingGroundsMissionPrefab = ToolBox.SelectWeightedRandom(huntingGroundsMissionPrefabs, p => (float)Math.Max(p.Commonness, 0.1f), rand);
|
||||
// Adjust the prefab commonness based on the difficulty tag
|
||||
var prefabs = huntingGroundsMissionPrefabs.ToList();
|
||||
var weights = prefabs.Select(p => (float)Math.Max(p.Commonness, 1)).ToList();
|
||||
for (int i = 0; i < prefabs.Count; i++)
|
||||
{
|
||||
var prefab = prefabs[i];
|
||||
var weight = weights[i];
|
||||
if (prefab.Tags.Contains("easy"))
|
||||
{
|
||||
weight *= MathHelper.Lerp(0.2f, 2f, MathUtils.InverseLerp(80, LevelData.HuntingGroundsDifficultyThreshold, levelData.Difficulty));
|
||||
}
|
||||
else if (prefab.Tags.Contains("hard"))
|
||||
{
|
||||
weight *= MathHelper.Lerp(0.5f, 1.5f, MathUtils.InverseLerp(LevelData.HuntingGroundsDifficultyThreshold + 10, 80, levelData.Difficulty));
|
||||
}
|
||||
weights[i] = weight;
|
||||
}
|
||||
var huntingGroundsMissionPrefab = ToolBox.SelectWeightedRandom(prefabs, weights, rand);
|
||||
if (!Missions.Any(m => m.Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations, Submarine.MainSub));
|
||||
|
||||
@@ -754,9 +754,9 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
IEnumerable<Character> crewCharacters = GetSessionCrewCharacters();
|
||||
ImmutableArray<Character> crewCharacters = GetSessionCrewCharacters().ToImmutableArray();
|
||||
|
||||
int prevMoney = (GameMode as CampaignMode)?.Bank.Balance ?? 0; // FIXME personal wallets - reward distribution
|
||||
int prevMoney = GetAmountOfMoney(crewCharacters);
|
||||
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
@@ -828,7 +828,7 @@ namespace Barotrauma
|
||||
LogEndRoundStats(eventId);
|
||||
if (GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", campaignMode.Bank.Balance - prevMoney); // FIXME personal wallets - reward distrubiton
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", GetAmountOfMoney(crewCharacters) - prevMoney);
|
||||
campaignMode.TotalPlayTime += roundDuration;
|
||||
}
|
||||
#if CLIENT
|
||||
@@ -840,6 +840,17 @@ namespace Barotrauma
|
||||
{
|
||||
RoundEnding = false;
|
||||
}
|
||||
|
||||
int GetAmountOfMoney(IEnumerable<Character> crew)
|
||||
{
|
||||
if (!(GameMode is CampaignMode campaign)) { return 0; }
|
||||
|
||||
return GameMain.NetworkMember switch
|
||||
{
|
||||
null => campaign.Bank.Balance,
|
||||
_ => crew.Sum(c => c.Wallet.Balance) + campaign.Bank.Balance
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void LogEndRoundStats(string eventId)
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Barotrauma
|
||||
price = 0;
|
||||
}
|
||||
|
||||
if (Campaign.GetWallet(client).TryDeduct(price)) // FIXME personal wallets
|
||||
if (Campaign.GetWallet(client).TryDeduct(price))
|
||||
{
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the OnUse StatusEffects trigger when docking (on vanilla docking ports these effects emit particles and play a sound).)")]
|
||||
[Editable, Serialize(true, IsPropertySaveable.No, description: "Should the OnUse StatusEffects trigger when docking (on vanilla docking ports these effects emit particles and play a sound).)")]
|
||||
public bool ApplyEffectsOnDocking
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -424,6 +424,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
//update when the item is broken too to get OnContaining effects to execute and contained item positions to update
|
||||
if (IsActive)
|
||||
{
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
hull.BallastFlora = new BallastFloraBehavior(hull, ballastFloraPrefab, offset, firstGrowth: true);
|
||||
|
||||
#if SERVER
|
||||
hull.BallastFlora.SendNetworkMessage(new BallastFloraBehavior.SpawnEventData());
|
||||
hull.BallastFlora.CreateNetworkMessage(new BallastFloraBehavior.SpawnEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
#if CLIENT
|
||||
@@ -151,20 +150,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
set
|
||||
{
|
||||
if (powerIn != null)
|
||||
{
|
||||
if (powerIn.Grid != null)
|
||||
{
|
||||
powerIn.Grid.Voltage = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
else if (powerOut != null)
|
||||
{
|
||||
if (powerOut.Grid != null)
|
||||
{
|
||||
powerOut.Grid.Voltage = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
voltage = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
@@ -213,11 +198,6 @@ namespace Barotrauma.Items.Components
|
||||
powerOnSoundPlayed = false;
|
||||
}
|
||||
#endif
|
||||
if (powerIn == null)
|
||||
{
|
||||
//power down the device here if it has no power connection (= receives power from contained battery cells instead of the "normal" power logic)
|
||||
Voltage -= deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -470,6 +450,11 @@ namespace Barotrauma.Items.Components
|
||||
//Determine if devices are adding a load or providing power, also resolve solo nodes
|
||||
foreach (Powered powered in poweredList)
|
||||
{
|
||||
//Make voltage decay to ensure the device powers down.
|
||||
//This only effects devices with no power input (whose voltage is set by other means, e.g. status effects from a contained battery)
|
||||
//or devices that have been disconnected from the power grid - other devices use the voltage of the grid instead.
|
||||
powered.Voltage -= deltaTime;
|
||||
|
||||
//Handle the device if it's got a power connection
|
||||
if (powered.powerIn != null && powered.powerOut != powered.powerIn)
|
||||
{
|
||||
@@ -500,7 +485,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
powered.CurrPowerConsumption = powered.GetConnectionPowerOut(powered.powerIn, 0, powered.MinMaxPowerOut(powered.powerIn, 0), 0);
|
||||
powered.CurrPowerConsumption = -powered.GetConnectionPowerOut(powered.powerIn, 0, powered.MinMaxPowerOut(powered.powerIn, 0), 0);
|
||||
powered.GridResolved(powered.powerIn);
|
||||
}
|
||||
}
|
||||
@@ -541,7 +526,7 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//Perform power calculations for the singular connection
|
||||
float loadOut = powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
|
||||
float loadOut = -powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
|
||||
if (powered is PowerTransfer pt2)
|
||||
{
|
||||
pt2.PowerLoad = loadOut;
|
||||
@@ -667,7 +652,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public static bool ValidPowerConnection(Connection conn1, Connection conn2)
|
||||
{
|
||||
return conn1.IsPower && conn2.IsPower && (conn1.Item.HasTag("junctionbox") || conn2.Item.HasTag("junctionbox") || conn1.IsOutput != conn2.IsOutput || (conn1.Item.HasTag("dock") && conn2.Item.HasTag("dock")));
|
||||
return conn1.IsPower && conn2.IsPower && (conn1.Item.HasTag("junctionbox") || conn2.Item.HasTag("junctionbox") || conn1.Item.HasTag("dock") || conn2.Item.HasTag("dock") || conn1.IsOutput != conn2.IsOutput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -49,8 +49,6 @@ namespace Barotrauma.Items.Components
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
//a duration during which the projectile won't drop from the body it's stuck to
|
||||
private const float PersistentStickJointDuration = 1.0f;
|
||||
private PrismaticJoint stickJoint;
|
||||
|
||||
public Attack Attack { get; private set; }
|
||||
@@ -86,8 +84,6 @@ namespace Barotrauma.Items.Components
|
||||
get { return hits; }
|
||||
}
|
||||
|
||||
private float persistentStickJointTimer;
|
||||
|
||||
[Serialize(10.0f, IsPropertySaveable.No, description: "The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
|
||||
public float LaunchImpulse { get; set; }
|
||||
|
||||
@@ -116,13 +112,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "When set to true, the item won't fall of a target it's stuck to unless removed.")]
|
||||
public bool StickPermanently
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item stick to the character it hits.")]
|
||||
public bool StickToCharacters
|
||||
{
|
||||
@@ -151,6 +140,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "")]
|
||||
public bool StickToLightTargets
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+
|
||||
"It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")]
|
||||
public bool Hitscan
|
||||
@@ -212,16 +208,29 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private float stickTimer;
|
||||
[Serialize(0f, IsPropertySaveable.No)]
|
||||
public float StickDuration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1f, IsPropertySaveable.No)]
|
||||
public float MaxJointTranslation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
private float _maxJointTranslation = -1;
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsStuckToTarget
|
||||
{
|
||||
get { return StickTarget != null; }
|
||||
}
|
||||
public bool IsStuckToTarget => StickTarget != null;
|
||||
|
||||
private Category originalCollisionCategories;
|
||||
private Category originalCollisionTargets;
|
||||
@@ -660,23 +669,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
if (persistentStickJointTimer > 0.0f && !StickPermanently)
|
||||
if (StickDuration > 0 && stickTimer > 0)
|
||||
{
|
||||
persistentStickJointTimer -= deltaTime;
|
||||
stickTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
float absoluteMaxTranslation = 100;
|
||||
// Update the item's transform to make sure it's inside the same sub as the target (or outside)
|
||||
if (StickTarget?.UserData is Limb target && target.Submarine != item.Submarine || Math.Abs(stickJoint.JointTranslation) > 100.0f)
|
||||
if (StickTarget?.UserData is Limb target && target.Submarine != item.Submarine || Math.Abs(stickJoint.JointTranslation) > absoluteMaxTranslation)
|
||||
{
|
||||
item.UpdateTransform();
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (StickTargetRemoved() ||
|
||||
(!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)) ||
|
||||
Math.Abs(stickJoint.JointTranslation) > 100.0f) //failsafe unstick if the target is still extremely far
|
||||
if (StickTargetRemoved() || Math.Abs(stickJoint.JointTranslation) > _maxJointTranslation)
|
||||
{
|
||||
Unstick();
|
||||
#if SERVER
|
||||
@@ -936,14 +944,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.body.LinearVelocity *= deflectedSpeedMultiplier;
|
||||
}
|
||||
else if ( // When hitting characters the collision normal seems to sometimes point into wrong direction, resulting in a failed attempt to stick
|
||||
//Vector2.Dot(Vector2.Normalize(velocity), collisionNormal) < 0.0f &&
|
||||
hits.Count() >= MaxTargetsToHit &&
|
||||
target.Body.Mass > item.body.Mass * 0.5f &&
|
||||
else if ( stickJoint == null && StickTarget == null &&
|
||||
StickToStructures && target.Body.UserData is Structure ||
|
||||
((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
|
||||
(DoesStick ||
|
||||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
|
||||
(StickToStructures && target.Body.UserData is Structure) ||
|
||||
(StickToItems && target.Body.UserData is Item)))
|
||||
(StickToItems && target.Body.UserData is Item))))
|
||||
{
|
||||
Vector2 dir = new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
@@ -1026,6 +1032,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (stickJoint != null) { return; }
|
||||
|
||||
item.body.ResetDynamics();
|
||||
|
||||
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true)
|
||||
{
|
||||
MotorEnabled = true,
|
||||
@@ -1034,18 +1042,17 @@ namespace Barotrauma.Items.Components
|
||||
Breakpoint = 1000.0f
|
||||
};
|
||||
|
||||
if (StickPermanently)
|
||||
if (_maxJointTranslation == -1)
|
||||
{
|
||||
stickJoint.LowerLimit = stickJoint.UpperLimit = 0.0f;
|
||||
item.body.ResetDynamics();
|
||||
}
|
||||
else if (item.Sprite != null)
|
||||
{
|
||||
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f * item.Scale);
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f * item.Scale);
|
||||
if (item.Sprite != null && MaxJointTranslation < 0)
|
||||
{
|
||||
MaxJointTranslation = item.Sprite.size.X / 2 * item.Scale;
|
||||
}
|
||||
MaxJointTranslation = Math.Min(MaxJointTranslation, 1000);
|
||||
_maxJointTranslation = ConvertUnits.ToSimUnits(MaxJointTranslation);
|
||||
}
|
||||
|
||||
persistentStickJointTimer = PersistentStickJointDuration;
|
||||
stickTimer = StickDuration;
|
||||
StickTarget = targetBody;
|
||||
GameMain.World.Add(stickJoint);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -12,8 +12,36 @@ namespace Barotrauma.Items.Components
|
||||
private ISpatialEntity source;
|
||||
private Item target;
|
||||
|
||||
private Vector2? launchDir;
|
||||
|
||||
private void SetSource(ISpatialEntity source)
|
||||
{
|
||||
this.source = source;
|
||||
if (source is Limb sourceLimb)
|
||||
{
|
||||
sourceLimb.AttachedRope = this;
|
||||
float offset = sourceLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
launchDir = VectorExtensions.Forward(sourceLimb.body.TransformedRotation - offset * sourceLimb.character.AnimController.Dir);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetSource()
|
||||
{
|
||||
if (source is Limb sourceLimb && sourceLimb.AttachedRope == this)
|
||||
{
|
||||
sourceLimb.AttachedRope = null;
|
||||
}
|
||||
source = null;
|
||||
}
|
||||
|
||||
private float snapTimer;
|
||||
private const float SnapAnimDuration = 1.0f;
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "")]
|
||||
public float SnapAnimDuration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private float raycastTimer;
|
||||
private const float RayCastInterval = 0.2f;
|
||||
@@ -46,6 +74,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(360.0f, IsPropertySaveable.No, description: "How far the source item can be from the projectile until the rope breaks.")]
|
||||
public float MaxAngle
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the rope snap when it collides with a structure/submarine (if not, it will just go through it).")]
|
||||
public bool SnapOnCollision
|
||||
{
|
||||
@@ -115,8 +150,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(source != null);
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
SetSource(source);
|
||||
Snapped = false;
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition);
|
||||
IsActive = true;
|
||||
@@ -127,7 +162,7 @@ namespace Barotrauma.Items.Components
|
||||
if (source == null || target == null || target.Removed ||
|
||||
(source is Entity sourceEntity && sourceEntity.Removed))
|
||||
{
|
||||
source = null;
|
||||
ResetSource();
|
||||
target = null;
|
||||
IsActive = false;
|
||||
return;
|
||||
@@ -144,12 +179,27 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Vector2 diff = target.WorldPosition - source.WorldPosition;
|
||||
if (diff.LengthSquared() > MaxLength * MaxLength)
|
||||
float lengthSqr = diff.LengthSquared();
|
||||
if (lengthSqr > MaxLength * MaxLength)
|
||||
{
|
||||
Snap();
|
||||
return;
|
||||
}
|
||||
|
||||
if (MaxAngle < 180 && lengthSqr > 2500)
|
||||
{
|
||||
if (launchDir == null)
|
||||
{
|
||||
launchDir = diff;
|
||||
}
|
||||
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(launchDir.Value, diff));
|
||||
if (angle > MaxAngle)
|
||||
{
|
||||
Snap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
|
||||
@@ -1677,12 +1677,17 @@ namespace Barotrauma
|
||||
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
|
||||
if (!conditionUpdatePending) { return; }
|
||||
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new StatusEventData());
|
||||
CreateStatusEvent();
|
||||
lastSentCondition = condition;
|
||||
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
|
||||
conditionUpdatePending = false;
|
||||
}
|
||||
|
||||
public void CreateStatusEvent()
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ItemStatusEventData());
|
||||
}
|
||||
|
||||
private bool isActive = true;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct StatusEventData : IEventData
|
||||
private readonly struct ItemStatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
}
|
||||
|
||||
@@ -520,8 +520,9 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
if (branch.Health > branch.MaxHealth * 0.9f || branch.DisconnectedFromRoot) { continue; }
|
||||
float branchHealAmount = (float)(MaxBranchHealthRegenDistance - branch.BranchDepth) / MaxBranchHealthRegenDistance * healAmount;
|
||||
if (branchHealAmount <= 0.0f) { continue; }
|
||||
float prevHealth = branch.Health;
|
||||
branch.Health += branchHealAmount;
|
||||
branch.AccumulatedDamage -= branchHealAmount;
|
||||
branch.AccumulatedDamage += (prevHealth - branch.Health);
|
||||
}
|
||||
}
|
||||
StateMachine.Update(deltaTime);
|
||||
@@ -633,7 +634,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
if (branch.ParentBranch != null && (branch.ParentBranch.DisconnectedFromRoot || branch.ParentBranch.Health <= 0.0f))
|
||||
{
|
||||
DamageBranch(branch, deltaTime * MathHelper.Lerp(10.0f, 0.01f, branch.ParentBranch.Health / branch.ParentBranch.MaxHealth), AttackType.CutFromRoot);
|
||||
float speed = MathHelper.Lerp(5.0f, 0.1f, branch.ParentBranch.Health / branch.ParentBranch.MaxHealth);
|
||||
DamageBranch(branch, speed * speed * deltaTime, AttackType.CutFromRoot);
|
||||
}
|
||||
if (branch.Health <= 0.0f)
|
||||
{
|
||||
@@ -836,7 +838,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
SendNetworkMessage(new BranchCreateEventData(newBranch, parent));
|
||||
CreateNetworkMessage(new BranchCreateEventData(newBranch, parent));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -878,7 +880,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
#if SERVER
|
||||
if (!load)
|
||||
{
|
||||
SendNetworkMessage(new InfectEventData(target, InfectEventData.InfectState.Yes, branch));
|
||||
CreateNetworkMessage(new InfectEventData(target, InfectEventData.InfectState.Yes, branch));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -955,8 +957,6 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
/// <param name="branch"></param>
|
||||
private void CreateBody(BallastFloraBranch branch)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
Rectangle rect = branch.Rect;
|
||||
Vector2 pos = Parent.Position + Offset + branch.Position;
|
||||
|
||||
@@ -975,6 +975,14 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public void DamageBranch(BallastFloraBranch branch, float amount, AttackType type, Character? attacker = null)
|
||||
{
|
||||
float damage = amount;
|
||||
if (damage > 0)
|
||||
{
|
||||
damage = Math.Min(damage, branch.Health);
|
||||
}
|
||||
else
|
||||
{
|
||||
damage = Math.Max(damage, branch.Health - branch.MaxHealth);
|
||||
}
|
||||
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
@@ -983,8 +991,28 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
if (branch.IsRootGrowth && root != null && root.Health > 0.0f) { return; }
|
||||
|
||||
// damage is handled server side currently
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
branch.AccumulatedDamage += damage;
|
||||
Anger += damage * 0.001f;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
// damage is handled server side
|
||||
if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//accumulate damage on the server's side to ensure clients get notified
|
||||
if (type == AttackType.Other || type == AttackType.CutFromRoot)
|
||||
{
|
||||
branch.AccumulatedDamage += damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attacker != null && toxinsCooldown <= 0)
|
||||
{
|
||||
@@ -1014,11 +1042,6 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
branch.Health -= damage;
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
branch.AccumulatedDamage += damage;
|
||||
Anger += damage * 0.001f;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
GameMain.Server?.KarmaManager?.OnBallastFloraDamaged(attacker, damage);
|
||||
@@ -1110,7 +1133,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
#if SERVER
|
||||
if (!wasRemoved)
|
||||
{
|
||||
SendNetworkMessage(new BranchRemoveEventData(branch));
|
||||
CreateNetworkMessage(new BranchRemoveEventData(branch));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1141,7 +1164,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
});
|
||||
#if SERVER
|
||||
SendNetworkMessage(new InfectEventData(item, InfectEventData.InfectState.No, null));
|
||||
CreateNetworkMessage(new InfectEventData(item, InfectEventData.InfectState.No, null));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1159,7 +1182,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
StateMachine?.State?.Exit();
|
||||
#if SERVER
|
||||
SendNetworkMessage(new KillEventData());
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1181,7 +1204,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
_entityList.Remove(this);
|
||||
#if SERVER
|
||||
SendNetworkMessage(new KillEventData());
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -160,17 +160,20 @@ namespace Barotrauma
|
||||
public void Delete()
|
||||
{
|
||||
Dispose();
|
||||
if (File.Exists(ContentFile.Path))
|
||||
try
|
||||
{
|
||||
try
|
||||
if (ContentPackage is { Files: { Length: 1 } }
|
||||
&& ContentPackageManager.LocalPackages.Contains(ContentPackage))
|
||||
{
|
||||
File.Delete(ContentFile.Path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Deleting item assembly \"" + Name + "\" failed.", e);
|
||||
Directory.Delete(ContentPackage.Dir, recursive: true);
|
||||
ContentPackageManager.LocalPackages.Refresh();
|
||||
ContentPackageManager.EnabledPackages.DisableRemovedMods();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Deleting item assembly \"" + Name + "\" failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
|
||||
@@ -3826,12 +3826,12 @@ namespace Barotrauma
|
||||
if (location != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Generating an outpost for the {(isStart ? "start" : "end")} of the level... (Location: {location.Name}, level type: {LevelData.Type})");
|
||||
outpost = OutpostGenerator.Generate(outpostGenerationParams, location, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost);
|
||||
outpost = OutpostGenerator.Generate(outpostGenerationParams, location, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost, LevelData.AllowInvalidOutpost);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Generating an outpost for the {(isStart ? "start" : "end")} of the level... (Location type: {locationType}, level type: {LevelData.Type})");
|
||||
outpost = OutpostGenerator.Generate(outpostGenerationParams, locationType, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost);
|
||||
outpost = OutpostGenerator.Generate(outpostGenerationParams, locationType, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost, LevelData.AllowInvalidOutpost);
|
||||
}
|
||||
|
||||
foreach (string categoryToHide in locationType.HideEntitySubcategories)
|
||||
|
||||
@@ -31,8 +31,16 @@ namespace Barotrauma
|
||||
|
||||
public bool HasHuntingGrounds, OriginallyHadHuntingGrounds;
|
||||
|
||||
//minimum difficulty of the level before hunting grounds can appear
|
||||
public const float HuntingGroundsDifficultyThreshold = 25;
|
||||
|
||||
//probability of hunting grounds appearing in 100% difficulty levels
|
||||
public const float MaxHuntingGroundsProbability = 0.3f;
|
||||
|
||||
public OutpostGenerationParams ForceOutpostGenerationParams;
|
||||
|
||||
public bool AllowInvalidOutpost;
|
||||
|
||||
public readonly Point Size;
|
||||
|
||||
public readonly int InitialDepth;
|
||||
@@ -150,11 +158,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
//minimum difficulty of the level before hunting grounds can appear
|
||||
float huntingGroundsDifficultyThreshold = 25;
|
||||
//probability of hunting grounds appearing in 100% difficulty levels
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(HuntingGroundsDifficultyThreshold, 100.0f, Difficulty) * MaxHuntingGroundsProbability;
|
||||
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
}
|
||||
IsBeaconActive = false;
|
||||
|
||||
@@ -182,8 +182,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
Connections[i].LevelData.HasHuntingGrounds = Rand.Range(0.0f, 1.0f) < Connections[i].Difficulty / 100.0f * maxHuntingGroundsProbability;
|
||||
Connections[i].LevelData.HasHuntingGrounds = Rand.Range(0.0f, 1.0f) < Connections[i].Difficulty / 100.0f * LevelData.MaxHuntingGroundsProbability;
|
||||
connectionElements[i].SetAttributeValue("hashuntinggrounds", true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,17 +57,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, bool onlyEntrance = false)
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, bool onlyEntrance = false, bool allowInvalidOutpost = false)
|
||||
{
|
||||
return Generate(generationParams, locationType, location: null, onlyEntrance);
|
||||
return Generate(generationParams, locationType, location: null, onlyEntrance, allowInvalidOutpost);
|
||||
}
|
||||
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, Location location, bool onlyEntrance = false)
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, Location location, bool onlyEntrance = false, bool allowInvalidOutpost = false)
|
||||
{
|
||||
return Generate(generationParams, location.Type, location, onlyEntrance);
|
||||
return Generate(generationParams, location.Type, location, onlyEntrance, allowInvalidOutpost);
|
||||
}
|
||||
|
||||
private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false)
|
||||
private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false, bool allowInvalidOutpost = false)
|
||||
{
|
||||
var outpostModuleFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<OutpostModuleFile>())
|
||||
@@ -197,12 +197,19 @@ namespace Barotrauma
|
||||
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams);
|
||||
if (pendingModuleFlags.Any(flag => flag != "none"))
|
||||
{
|
||||
remainingTries--;
|
||||
if (remainingTries <= 0)
|
||||
if (!allowInvalidOutpost)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough doors at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags));
|
||||
remainingTries--;
|
||||
if (remainingTries <= 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough connections at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough connections at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags) + ". Won't retry because invalid outposts are allowed.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var outpostInfo = new SubmarineInfo()
|
||||
@@ -328,7 +335,10 @@ namespace Barotrauma
|
||||
selectedModule.Offset =
|
||||
(selectedModule.PreviousGap.WorldPosition + selectedModule.PreviousModule.Offset) -
|
||||
selectedModule.ThisGap.WorldPosition;
|
||||
selectedModule.Offset += moveDir * generationParams.MinHallwayLength;
|
||||
if (selectedModule.PreviousGap.ConnectedDoor != null || selectedModule.ThisGap.ConnectedDoor != null)
|
||||
{
|
||||
selectedModule.Offset += moveDir * generationParams.MinHallwayLength;
|
||||
}
|
||||
}
|
||||
entities[selectedModule] = moduleEntities;
|
||||
}
|
||||
@@ -464,6 +474,16 @@ namespace Barotrauma
|
||||
pendingModuleFlags.Remove(initialModuleFlag);
|
||||
pendingModuleFlags.Insert(0, initialModuleFlag);
|
||||
|
||||
if (pendingModuleFlags.Count > totalModuleCount)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error during outpost generation. {pendingModuleFlags.Count} modules set to be used the outpost, but total module count is only {totalModuleCount}. Leaving out some of the modules...");
|
||||
int removeCount = pendingModuleFlags.Count - totalModuleCount;
|
||||
for (int i = 0; i < removeCount; i++)
|
||||
{
|
||||
pendingModuleFlags.Remove(pendingModuleFlags.Last());
|
||||
}
|
||||
}
|
||||
|
||||
return pendingModuleFlags;
|
||||
}
|
||||
|
||||
@@ -486,46 +506,71 @@ namespace Barotrauma
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
|
||||
List<PlacedModule> placedModules = new List<PlacedModule>();
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions.Randomize(Rand.RandSync.ServerAndClient))
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
if (!allowExtendBelowInitialModule)
|
||||
//try placing a module meant for this location type first, and if that fails, try choosing whatever fits
|
||||
bool allowDifferentLocationType = i > 0;
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions.Randomize(Rand.RandSync.ServerAndClient))
|
||||
{
|
||||
//don't continue downwards if it'd extend below the airlock
|
||||
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
|
||||
}
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
var newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
if (newModule != null) { placedModules.Add(newModule); }
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
if (!allowExtendBelowInitialModule)
|
||||
{
|
||||
//don't continue downwards if it'd extend below the airlock
|
||||
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
|
||||
}
|
||||
|
||||
PlacedModule newModule = null;
|
||||
//try appending to the current module if possible
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
}
|
||||
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
//couldn't append to current module, try one of the other placed modules
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (otherModule == currentModule) { continue; }
|
||||
foreach (OutpostModuleInfo.GapPosition otherGapPosition in
|
||||
GapPositions.Where(g => !otherModule.UsedGapPositions.HasFlag(g) && otherModule.Info.OutpostModuleInfo.GapPositions.HasFlag(g)))
|
||||
{
|
||||
newModule = AppendModule(otherModule, GetOpposingGapPosition(otherGapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newModule != null) { break; }
|
||||
}
|
||||
}
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
//couldn't place anything, retry
|
||||
if (placedModules.Count == 0 && retry && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule.PreviousModule))
|
||||
//couldn't place a module anywhere, we're probably fucked!
|
||||
if (placedModules.Count == 0 && retry && currentModule.PreviousModule != null && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule))
|
||||
{
|
||||
//try to append to some other module first
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//try to replace the previously placed module with something else that we can append to
|
||||
var failedModule = currentModule;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
selectedModules.Remove(currentModule);
|
||||
assertAllPreviousModulesPresent();
|
||||
//readd the module types that the previous module was supposed to fulfill to the pending module types
|
||||
pendingModuleFlags.AddRange(currentModule.FulfilledModuleTypes);
|
||||
if (!availableModules.Contains(currentModule.Info)) { availableModules.Add(currentModule.Info); }
|
||||
//retry
|
||||
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType: true);
|
||||
assertAllPreviousModulesPresent();
|
||||
if (currentModule == null) { break; }
|
||||
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
{
|
||||
assertAllPreviousModulesPresent();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -534,9 +579,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (PlacedModule placedModule in placedModules)
|
||||
{
|
||||
AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: allowExtendBelowInitialModule);
|
||||
}
|
||||
return placedModules.Count > 0;
|
||||
|
||||
void assertAllPreviousModulesPresent()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(selectedModules.All(m => m.PreviousModule == null || selectedModules.Contains(m.PreviousModule)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -553,7 +603,8 @@ namespace Barotrauma
|
||||
List<SubmarineInfo> availableModules,
|
||||
List<Identifier> pendingModuleFlags,
|
||||
List<PlacedModule> selectedModules,
|
||||
LocationType locationType)
|
||||
LocationType locationType,
|
||||
bool allowDifferentLocationType)
|
||||
{
|
||||
if (pendingModuleFlags.Count == 0) { return null; }
|
||||
|
||||
@@ -562,7 +613,7 @@ namespace Barotrauma
|
||||
foreach (Identifier moduleFlag in pendingModuleFlags)
|
||||
{
|
||||
flagToPlace = moduleFlag;
|
||||
nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType);
|
||||
nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType, allowDifferentLocationType);
|
||||
if (nextModule != null) { break; }
|
||||
}
|
||||
|
||||
@@ -603,6 +654,7 @@ namespace Barotrauma
|
||||
foreach (PlacedModule otherModule in modules2)
|
||||
{
|
||||
if (module == otherModule) { continue; }
|
||||
if (module.PreviousModule == otherModule && module.PreviousGap.ConnectedDoor == null && module.ThisGap.ConnectedDoor == null) { continue; }
|
||||
if (ModulesOverlap(module, otherModule))
|
||||
{
|
||||
module1 = module;
|
||||
@@ -775,22 +827,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType)
|
||||
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType, bool allowDifferentLocationType)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> availableModules = null;
|
||||
if (moduleFlag.IsEmpty || moduleFlag.Equals("none"))
|
||||
{
|
||||
availableModules = modules
|
||||
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier())) && m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition));
|
||||
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier())));
|
||||
}
|
||||
else
|
||||
{
|
||||
availableModules = modules
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag) && m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition));
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
}
|
||||
|
||||
availableModules = availableModules.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
|
||||
|
||||
if (prevModule != null)
|
||||
{
|
||||
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule) && CanAttachTo(prevModule, m.OutpostModuleInfo));
|
||||
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));// && CanAttachTo(prevModule, m.OutpostModuleInfo));
|
||||
}
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
@@ -800,15 +855,22 @@ namespace Barotrauma
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
if (allowDifferentLocationType && !modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
if (allowDifferentLocationType)
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1039,16 +1101,20 @@ namespace Barotrauma
|
||||
|
||||
if (hallwayLength <= 1.0f) { continue; }
|
||||
|
||||
var suitableModules = availableModules.Where(m =>
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.Info.OutpostModuleInfo.ModuleFlags.Contains(s)) &&
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.PreviousModule.Info.OutpostModuleInfo.ModuleFlags.Contains(s)));
|
||||
if (suitableModules.Count() == 0)
|
||||
Identifier moduleFlag = (isHorizontal ? "hallwayhorizontal" : "hallwayvertical").ToIdentifier();
|
||||
var hallwayModules = availableModules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
|
||||
var suitableHallwayModules = hallwayModules.Where(m =>
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.Info.OutpostModuleInfo.ModuleFlags.Contains(s)) &&
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.PreviousModule.Info.OutpostModuleInfo.ModuleFlags.Contains(s)));
|
||||
if (suitableHallwayModules.Count() == 0)
|
||||
{
|
||||
suitableModules = availableModules.Where(m =>
|
||||
suitableHallwayModules = hallwayModules.Where(m =>
|
||||
!m.OutpostModuleInfo.AllowAttachToModules.Any() ||
|
||||
m.OutpostModuleInfo.AllowAttachToModules.All(s => s == "any"));
|
||||
}
|
||||
var hallwayInfo = GetRandomModule(suitableModules, (isHorizontal ? "hallwayhorizontal" : "hallwayvertical").ToIdentifier(), locationType);
|
||||
|
||||
var hallwayInfo = GetRandomModule(suitableHallwayModules, moduleFlag, locationType);
|
||||
if (hallwayInfo == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Generating hallways between outpost modules failed. No {(isHorizontal ? "horizontal" : "vertical")} hallway modules suitable for use between the modules \"{module.Info.DisplayName}\" and \"{module.PreviousModule.Info.DisplayName}\".");
|
||||
@@ -1170,7 +1236,7 @@ namespace Barotrauma
|
||||
var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.ThisGap);
|
||||
if (startWaypoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.Info.Name}\".");
|
||||
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {module.ThisGapPosition.ToString().ToLower()} gap of the module \"{module.Info.Name}\".");
|
||||
continue;
|
||||
}
|
||||
var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.PreviousGap);
|
||||
|
||||
@@ -45,6 +45,9 @@ namespace Barotrauma
|
||||
[Serialize(GapPosition.None, IsPropertySaveable.Yes, description: "Which sides of the module have gaps on them (i.e. from which sides the module can be attached to other modules). Center = no gaps available.")]
|
||||
public GapPosition GapPositions { get; set; }
|
||||
|
||||
[Serialize(GapPosition.Right | GapPosition.Left | GapPosition.Bottom | GapPosition.Top, IsPropertySaveable.Yes, description: "Which sides of this module are allowed to attach to the previously placed module. E.g. if you want a module to always attach to the left side of the docking module, you could set this to Right.")]
|
||||
public GapPosition CanAttachToPrevious { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
@@ -48,11 +48,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!subs.Any()) yield return CoroutineStatus.Success;
|
||||
|
||||
Character.Controlled = null;
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
#if CLIENT
|
||||
Character.Controlled = null;
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
#endif
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
|
||||
Level.Loaded.TopBarrier.Enabled = false;
|
||||
|
||||
|
||||
@@ -322,7 +322,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
|
||||
Tags = tags.ToImmutableHashSet();
|
||||
AllowedLinks = Enumerable.Empty<Identifier>().ToImmutableHashSet();
|
||||
AllowedLinks = ImmutableHashSet<Identifier>.Empty;
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma.Networking
|
||||
ERROR, //tell the server that an error occurred
|
||||
CREW, //hiring UI
|
||||
MEDICAL, //medical clinic
|
||||
MONEY, //wallet updates
|
||||
TRANSFER_MONEY, // wallet transfers
|
||||
REWARD_DISTRIBUTION, // wallet reward distribution
|
||||
READY_CHECK,
|
||||
READY_TO_SPAWN
|
||||
|
||||
@@ -172,11 +172,11 @@ namespace Barotrauma
|
||||
return !texName.IsNullOrEmpty() & !texName.Contains("/") && !texName.Contains("%ModDir", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static ContentPath GetAttributeContentPath(this XElement element, string name,
|
||||
ContentPackage contentPackage)
|
||||
public static ContentPath GetAttributeContentPath(this XElement element, string name, ContentPackage contentPackage)
|
||||
{
|
||||
if (element?.GetAttribute(name) == null) { return null; }
|
||||
return ContentPath.FromRaw(contentPackage, GetAttributeString(element.GetAttribute(name), null));
|
||||
var attribute = element?.GetAttribute(name);
|
||||
if (attribute == null) { return null; }
|
||||
return ContentPath.FromRaw(contentPackage, GetAttributeString(attribute, null));
|
||||
}
|
||||
|
||||
public static Identifier GetAttributeIdentifier(this XElement element, string name, string defaultValue)
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
#if CLIENT
|
||||
using Barotrauma.ClientSource.Settings;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
#endif
|
||||
|
||||
@@ -453,8 +454,12 @@ namespace Barotrauma
|
||||
|
||||
bool languageChanged = currentConfig.Language != newConfig.Language;
|
||||
|
||||
bool audioOutputChanged = currentConfig.Audio.AudioOutputDevice != newConfig.Audio.AudioOutputDevice;
|
||||
bool voiceCaptureChanged = currentConfig.Audio.VoiceCaptureDevice != newConfig.Audio.VoiceCaptureDevice;
|
||||
|
||||
bool textScaleChanged = Math.Abs(currentConfig.Graphics.TextScale - newConfig.Graphics.TextScale) > MathF.Pow(2.0f, -7);
|
||||
|
||||
currentConfig = newConfig;
|
||||
#warning TODO: Implement program state updates;
|
||||
|
||||
#if CLIENT
|
||||
if (setGraphicsMode)
|
||||
@@ -462,6 +467,24 @@ namespace Barotrauma
|
||||
GameMain.Instance.ApplyGraphicsSettings();
|
||||
}
|
||||
|
||||
if (audioOutputChanged)
|
||||
{
|
||||
GameMain.SoundManager?.InitializeAlcDevice(currentConfig.Audio.AudioOutputDevice);
|
||||
}
|
||||
|
||||
if (voiceCaptureChanged)
|
||||
{
|
||||
VoipCapture.ChangeCaptureDevice(currentConfig.Audio.VoiceCaptureDevice);
|
||||
}
|
||||
|
||||
if (textScaleChanged)
|
||||
{
|
||||
foreach (var font in GUIStyle.Fonts.Values)
|
||||
{
|
||||
font.Prefabs.ForEach(p => p.LoadFont());
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.SoundManager?.ApplySettings();
|
||||
#endif
|
||||
if (languageChanged) { TextManager.ClearCache(); }
|
||||
|
||||
@@ -68,15 +68,21 @@ namespace Barotrauma
|
||||
|
||||
public bool IsTriggered { get; private set; }
|
||||
|
||||
public float Timer { get; private set; } = -1;
|
||||
public float Timer { get; private set; }
|
||||
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
public bool IsPermanent { get; private set; }
|
||||
|
||||
public void Launch()
|
||||
{
|
||||
IsTriggered = true;
|
||||
IsActive = true;
|
||||
Timer = Duration;
|
||||
IsPermanent = Duration <= 0;
|
||||
if (!IsPermanent)
|
||||
{
|
||||
Timer = Duration;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
@@ -88,6 +94,7 @@ namespace Barotrauma
|
||||
|
||||
public void UpdateTimer(float deltaTime)
|
||||
{
|
||||
if (IsPermanent) { return; }
|
||||
Timer -= deltaTime;
|
||||
if (Timer < 0)
|
||||
{
|
||||
@@ -1243,24 +1250,27 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
if (targets[i] is Character character)
|
||||
var target = targets[i];
|
||||
Limb targetLimb = target as Limb;
|
||||
if (targetLimb == null && target is Character character)
|
||||
{
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.body == sourceBody)
|
||||
{
|
||||
targetLimb = limb;
|
||||
if (breakLimb)
|
||||
{
|
||||
character.TrySeverLimbJoints(limb, severLimbsProbability: 100, damage: 100, allowBeheading: true, attacker: user);
|
||||
}
|
||||
else
|
||||
{
|
||||
limb.HideAndDisable(hideLimbTimer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hideLimb)
|
||||
{
|
||||
targetLimb?.HideAndDisable(hideLimbTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.IO
|
||||
{
|
||||
static class Validation
|
||||
{
|
||||
private static readonly string[] unwritableDirs = new string[] { "Content" };
|
||||
private static readonly string[] unwritableExtensions = new string[]
|
||||
private static readonly ImmutableArray<Identifier> unwritableDirs = new[] { "Content".ToIdentifier() }.ToImmutableArray();
|
||||
private static readonly ImmutableArray<Identifier> unwritableExtensions = new[]
|
||||
{
|
||||
".pdb", ".com", ".scr", ".dylib", ".so", ".a", ".app", //executables and libraries (.exe and .dll handled separately in CanWrite)
|
||||
".pdb", ".com", ".scr", ".dylib", ".so", ".a", ".app", //executables and libraries (.exe, .dll and .json handled separately in CanWrite)
|
||||
".bat", ".sh", //shell scripts
|
||||
".json" //deps.json
|
||||
};
|
||||
}.ToIdentifiers().ToImmutableArray();
|
||||
|
||||
/// <summary>
|
||||
/// When set to true, the game is allowed to modify the vanilla content in debug builds. Has no effect in non-debug builds.
|
||||
@@ -24,25 +24,27 @@ namespace Barotrauma.IO
|
||||
public static bool CanWrite(string path, bool isDirectory)
|
||||
{
|
||||
path = System.IO.Path.GetFullPath(path).CleanUpPath();
|
||||
string localModsDir = System.IO.Path.GetFullPath(ContentPackage.LocalModsDir).CleanUpPath();
|
||||
string workshopModsDir = System.IO.Path.GetFullPath(ContentPackage.WorkshopModsDir).CleanUpPath();
|
||||
|
||||
if (!isDirectory)
|
||||
{
|
||||
string extension = System.IO.Path.GetExtension(path).Replace(" ", "");
|
||||
if (unwritableExtensions.Any(e => e.Equals(extension, StringComparison.OrdinalIgnoreCase)))
|
||||
Identifier extension = System.IO.Path.GetExtension(path).Replace(" ", "").ToIdentifier();
|
||||
if (unwritableExtensions.Any(e => e == extension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!path.StartsWith(System.IO.Path.GetFullPath("Mods/").CleanUpPath(), StringComparison.OrdinalIgnoreCase)
|
||||
&& (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)
|
||||
|| extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)))
|
||||
if (!path.StartsWith(workshopModsDir, StringComparison.OrdinalIgnoreCase)
|
||||
&& !path.StartsWith(localModsDir, StringComparison.OrdinalIgnoreCase)
|
||||
&& (extension == ".dll" || extension == ".exe" || extension == ".json"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string unwritableDir in unwritableDirs)
|
||||
foreach (var unwritableDir in unwritableDirs)
|
||||
{
|
||||
string dir = System.IO.Path.GetFullPath(unwritableDir).CleanUpPath();
|
||||
string dir = System.IO.Path.GetFullPath(unwritableDir.Value).CleanUpPath();
|
||||
|
||||
if (path.StartsWith(dir, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user