2f107db...5202af9
This commit is contained in:
@@ -12,7 +12,17 @@ namespace Barotrauma
|
||||
|
||||
private AIState state;
|
||||
|
||||
protected AITarget selectedAiTarget;
|
||||
protected AITarget _previousAiTarget;
|
||||
protected AITarget _selectedAiTarget;
|
||||
public AITarget SelectedAiTarget
|
||||
{
|
||||
get { return _selectedAiTarget; }
|
||||
protected set
|
||||
{
|
||||
_previousAiTarget = _selectedAiTarget;
|
||||
_selectedAiTarget = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected SteeringManager steeringManager;
|
||||
|
||||
@@ -57,11 +67,6 @@ namespace Barotrauma
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public AITarget SelectedAiTarget
|
||||
{
|
||||
get { return selectedAiTarget; }
|
||||
}
|
||||
|
||||
public AIState State
|
||||
{
|
||||
get { return state; }
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
public float SightRange
|
||||
{
|
||||
get { return sightRange; }
|
||||
set { sightRange = MathHelper.Clamp(value, MinSightRange, MaxSoundRange); }
|
||||
set { sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange); }
|
||||
}
|
||||
|
||||
private float sectorRad = MathHelper.TwoPi;
|
||||
@@ -58,8 +58,8 @@ namespace Barotrauma
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
public readonly float MinSoundRange, MinSightRange;
|
||||
public readonly float MaxSoundRange = float.MaxValue, MaxSightRange = float.MaxValue;
|
||||
public float MinSoundRange, MinSightRange;
|
||||
public float MaxSoundRange = float.MaxValue, MaxSightRange = float.MaxValue;
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
@@ -101,19 +101,12 @@ namespace Barotrauma
|
||||
|
||||
public AITarget(Entity e, XElement element) : this(e)
|
||||
{
|
||||
SightRange = MinSightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
||||
SoundRange = MinSoundRange = element.GetAttributeFloat("soundrange", 0.0f);
|
||||
// Use the min and max definitions if found.
|
||||
if (element.Attribute("minsightrange") != null)
|
||||
{
|
||||
MinSightRange = element.GetAttributeFloat("minsightrange", MinSightRange);
|
||||
}
|
||||
if (element.Attribute("minsoundrange") != null)
|
||||
{
|
||||
MinSoundRange = element.GetAttributeFloat("minsoundrange", MinSoundRange);
|
||||
}
|
||||
MaxSightRange = element.GetAttributeFloat("maxsightrange", MaxSightRange);
|
||||
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", MaxSoundRange);
|
||||
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
||||
SoundRange = element.GetAttributeFloat("soundrange", 0.0f);
|
||||
MinSightRange = element.GetAttributeFloat("minsightrange", SightRange);
|
||||
MinSoundRange = element.GetAttributeFloat("minsoundrange", SoundRange);
|
||||
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
|
||||
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
|
||||
SonarLabel = element.GetAttributeString("sonarlabel", "");
|
||||
}
|
||||
|
||||
|
||||
@@ -88,9 +88,7 @@ namespace Barotrauma
|
||||
|
||||
private AITargetMemory selectedTargetMemory;
|
||||
private float targetValue;
|
||||
|
||||
private float eatTimer;
|
||||
|
||||
|
||||
private Dictionary<AITarget, AITargetMemory> targetMemories;
|
||||
|
||||
//the eyesight of the NPC (0.0 = blind, 1.0 = sees every target within sightRange)
|
||||
@@ -250,7 +248,7 @@ namespace Barotrauma
|
||||
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = FindTargetMemory(target);
|
||||
|
||||
targetValue = 100.0f;
|
||||
@@ -292,7 +290,7 @@ namespace Barotrauma
|
||||
UpdateTargets(Character, out targetingPriority);
|
||||
updateTargetsTimer = UpdateTargetsInterval;
|
||||
|
||||
if (selectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
}
|
||||
@@ -308,7 +306,7 @@ namespace Barotrauma
|
||||
|
||||
latchOntoAI?.Update(this, deltaTime);
|
||||
|
||||
if (selectedAiTarget != null && (selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed))
|
||||
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -368,9 +366,9 @@ namespace Barotrauma
|
||||
|
||||
if (wallTarget != null) return;
|
||||
|
||||
if (selectedAiTarget != null)
|
||||
if (SelectedAiTarget != null)
|
||||
{
|
||||
Vector2 targetSimPos = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
|
||||
Vector2 targetSimPos = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f, speed);
|
||||
steeringManager.SteeringSeek(targetSimPos, speed);
|
||||
@@ -392,13 +390,13 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateEscape(float deltaTime)
|
||||
{
|
||||
if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed)
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 escapeDir = Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition);
|
||||
Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
float speed = Character.AnimController.GetCurrentSpeed(useMaxSpeed: true);
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir * speed);
|
||||
@@ -415,7 +413,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateAttack(float deltaTime)
|
||||
{
|
||||
if (selectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -425,23 +423,23 @@ namespace Barotrauma
|
||||
|
||||
selectedTargetMemory.Priority -= deltaTime * 0.1f;
|
||||
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
if (Character.Submarine != null && selectedAiTarget.Entity.Submarine != null && Character.Submarine != selectedAiTarget.Entity.Submarine)
|
||||
if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine != null && Character.Submarine != SelectedAiTarget.Entity.Submarine)
|
||||
{
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition - Character.Submarine.Position);
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition - Character.Submarine.Position);
|
||||
}
|
||||
|
||||
if (selectedAiTarget.Entity is Item item)
|
||||
if (SelectedAiTarget.Entity is Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.components.Select(c => c as Pickable).FirstOrDefault();
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
{
|
||||
var target = pickable.Picker?.AiTarget;
|
||||
if (target != null)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,13 +447,13 @@ namespace Barotrauma
|
||||
if (wallTarget != null)
|
||||
{
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position);
|
||||
if (Character.Submarine == null && selectedAiTarget.Entity?.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
|
||||
if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
|
||||
}
|
||||
else if (selectedAiTarget.Entity is Character)
|
||||
else if (SelectedAiTarget.Entity is Character)
|
||||
{
|
||||
//target the closest limb if the target is a character
|
||||
float closestDist = Vector2.DistanceSquared(selectedAiTarget.SimPosition, SimPosition) * 10.0f;
|
||||
foreach (Limb limb in ((Character)selectedAiTarget.Entity).AnimController.Limbs)
|
||||
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f;
|
||||
foreach (Limb limb in ((Character)SelectedAiTarget.Entity).AnimController.Limbs)
|
||||
{
|
||||
if (limb == null) continue;
|
||||
float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
@@ -514,9 +512,9 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (selectedAiTarget.Entity is Item)
|
||||
else if (SelectedAiTarget.Entity is Item)
|
||||
{
|
||||
var door = ((Item)selectedAiTarget.Entity).GetComponent<Door>();
|
||||
var door = ((Item)SelectedAiTarget.Entity).GetComponent<Door>();
|
||||
//steer through the door manually if it's open or broken
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f))
|
||||
{
|
||||
@@ -566,24 +564,39 @@ namespace Barotrauma
|
||||
{
|
||||
if (attackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var previousLimb = attackingLimb;
|
||||
var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
|
||||
if (newLimb != null)
|
||||
// Don't allow attacking when the attack target has changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
attackingLimb = newLimb;
|
||||
canAttack = false;
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
{
|
||||
// Fall back if cannot attack.
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
return;
|
||||
}
|
||||
attackingLimb = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var previousLimb = attackingLimb;
|
||||
var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
canAttack = false;
|
||||
attackingLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
return;
|
||||
// No new limb was found.
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
{
|
||||
canAttack = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,12 +661,12 @@ namespace Barotrauma
|
||||
else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null)
|
||||
{
|
||||
wallTarget = null;
|
||||
selectedAiTarget = indoorsSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget;
|
||||
SelectedAiTarget = indoorsSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget;
|
||||
}
|
||||
else if (indoorsSteering.CurrentPath.NextNode?.ConnectedDoor != null)
|
||||
{
|
||||
wallTarget = null;
|
||||
selectedAiTarget = indoorsSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget;
|
||||
SelectedAiTarget = indoorsSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -668,7 +681,7 @@ namespace Barotrauma
|
||||
private Limb GetAttackLimb(Vector2 attackSimPosition, Limb ignoredLimb = null)
|
||||
{
|
||||
AttackContext currentContext = Character.GetAttackContext();
|
||||
var target = wallTarget != null ? wallTarget.Structure : selectedAiTarget.Entity;
|
||||
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
|
||||
var limbs = Character.AnimController.Limbs
|
||||
.Where(l =>
|
||||
l != ignoredLimb &&
|
||||
@@ -677,7 +690,7 @@ namespace Barotrauma
|
||||
!l.IsStuck &&
|
||||
l.attack.IsValidContext(currentContext) &&
|
||||
l.attack.IsValidTarget(target) &&
|
||||
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity)))
|
||||
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character)))
|
||||
.OrderByDescending(l => l.attack.Priority)
|
||||
.ThenBy(l => ConvertUnits.ToDisplayUnits(Vector2.Distance(l.SimPosition, attackSimPosition)));
|
||||
// TODO: priority should probably not override the distance -> use values instead of booleans
|
||||
@@ -695,11 +708,11 @@ namespace Barotrauma
|
||||
|
||||
//check if there's a wall between the target and the Character
|
||||
Vector2 rayStart = Character.SimPosition;
|
||||
Vector2 rayEnd = selectedAiTarget.SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
|
||||
if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
rayStart -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
|
||||
rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
|
||||
}
|
||||
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
|
||||
@@ -795,7 +808,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition, float distance = -1)
|
||||
{
|
||||
var damageTarget = wallTarget != null ? wallTarget.Structure : selectedAiTarget.Entity as IDamageable;
|
||||
var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
|
||||
if (damageTarget == null) return;
|
||||
|
||||
float prevHealth = damageTarget.Health;
|
||||
@@ -834,7 +847,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateEating(float deltaTime)
|
||||
{
|
||||
if (selectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -850,13 +863,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 mouthPos = Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
float limbDist = limbDiff.Length();
|
||||
if (limbDist < 2.0f)
|
||||
{
|
||||
Character.SelectCharacter(selectedAiTarget.Entity as Character);
|
||||
Character.SelectCharacter(SelectedAiTarget.Entity as Character);
|
||||
steeringManager.SteeringManual(deltaTime, limbDiff);
|
||||
Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos);
|
||||
}
|
||||
@@ -875,10 +888,8 @@ namespace Barotrauma
|
||||
//sight/hearing range
|
||||
public void UpdateTargets(Character character, out TargetingPriority targetingPriority)
|
||||
{
|
||||
var prevAiTarget = selectedAiTarget;
|
||||
|
||||
targetingPriority = null;
|
||||
selectedAiTarget = null;
|
||||
SelectedAiTarget = null;
|
||||
selectedTargetMemory = null;
|
||||
targetValue = 0.0f;
|
||||
|
||||
@@ -936,11 +947,9 @@ namespace Barotrauma
|
||||
//skip the target if it's a room and the character is already inside a sub
|
||||
if (character.CurrentHull != null && target.Entity is Hull) continue;
|
||||
|
||||
//multiply the priority of the target if it's a door from outside to inside and the AI is an aggressive boarder
|
||||
Door door = null;
|
||||
if (target.Entity is Item item)
|
||||
{
|
||||
|
||||
//item inside and we're outside -> attack the hull
|
||||
if (item.CurrentHull != null && character.CurrentHull == null)
|
||||
{
|
||||
@@ -963,21 +972,20 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (door != null)
|
||||
{
|
||||
{
|
||||
//increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
|
||||
if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom)
|
||||
{
|
||||
valueModifier = door.IsOpen ? 5.0f : 3.0f;
|
||||
valueModifier = door.IsOpen ? 10 : 5;
|
||||
}
|
||||
else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (target.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f)
|
||||
{
|
||||
IDamageable targetDamageable = target.Entity as IDamageable;
|
||||
if (targetDamageable != null && targetDamageable.Health <= 0.0f) continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,7 +996,8 @@ namespace Barotrauma
|
||||
|
||||
if (valueModifier == 0.0f) continue;
|
||||
|
||||
dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
|
||||
Vector2 toTarget = target.WorldPosition - character.WorldPosition;
|
||||
dist = toTarget.Length();
|
||||
|
||||
//if the target has been within range earlier, the character will notice it more easily
|
||||
//(i.e. remember where the target was)
|
||||
@@ -1003,21 +1012,27 @@ namespace Barotrauma
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
|
||||
AITargetMemory targetMemory = FindTargetMemory(target);
|
||||
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
|
||||
{
|
||||
// Inside the sub, treat objects that are up or down, as they were farther away.
|
||||
dist *= 3;
|
||||
}
|
||||
valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist);
|
||||
|
||||
if (valueModifier > targetValue)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetingPriority = targetingPriorities[targetingTag];
|
||||
targetValue = valueModifier;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedAiTarget != prevAiTarget)
|
||||
if (SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
wallTarget = null;
|
||||
}
|
||||
}
|
||||
_previousAiTarget = SelectedAiTarget;
|
||||
}
|
||||
|
||||
//find the targetMemory that corresponds to some AItarget or create if there isn't one yet
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (DisableCrewAI || Character.IsUnconscious) return;
|
||||
|
||||
if (Character.Submarine != null || selectedAiTarget?.Entity?.Submarine != null)
|
||||
if (Character.Submarine != null || SelectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
if (steeringManager != insideSteering) insideSteering.Reset();
|
||||
steeringManager = insideSteering;
|
||||
@@ -122,9 +122,11 @@ namespace Barotrauma
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
if (run || Character.SpeedMultiplier <= 0.0f) targetMovement *= Character.SpeedMultiplier;
|
||||
|
||||
Character.SpeedMultiplier = 1.0f; // Reset, items will set the value before the next update
|
||||
|
||||
float speedMultiplier = Character.SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
|
||||
Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
|
||||
Character.AnimController.TargetMovement = targetMovement;
|
||||
}
|
||||
@@ -225,6 +227,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void ReportProblems();
|
||||
|
||||
private void UpdateSpeaking()
|
||||
{
|
||||
if (Character.Oxygen < 20.0f)
|
||||
@@ -277,7 +281,23 @@ namespace Barotrauma
|
||||
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
|
||||
private void CheckCrouching(float deltaTime)
|
||||
{
|
||||
crouchRaycastTimer -= deltaTime;
|
||||
if (crouchRaycastTimer > 0.0f) return;
|
||||
|
||||
crouchRaycastTimer = CrouchRaycastInterval;
|
||||
|
||||
//start the raycast in front of the character in the direction it's heading to
|
||||
Vector2 startPos = Character.SimPosition;
|
||||
startPos.X += MathHelper.Clamp(Character.AnimController.TargetMovement.X, -1.0f, 1.0f);
|
||||
|
||||
//do a raycast upwards to find any walls
|
||||
float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f;
|
||||
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null;
|
||||
}
|
||||
|
||||
private void CheckCrouching(float deltaTime)
|
||||
|
||||
@@ -18,6 +18,10 @@ namespace Barotrauma
|
||||
|
||||
private float findPathTimer;
|
||||
|
||||
private float buttonPressCooldown;
|
||||
|
||||
const float ButtonPressInterval = 0.5f;
|
||||
|
||||
public SteeringPath CurrentPath
|
||||
{
|
||||
get { return currentPath; }
|
||||
@@ -57,6 +61,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Update(speed);
|
||||
|
||||
buttonPressCooldown -= 1.0f / 60.0f;
|
||||
findPathTimer -= 1.0f / 60.0f;
|
||||
}
|
||||
|
||||
@@ -127,7 +132,7 @@ namespace Barotrauma
|
||||
return currentTarget - pos2;
|
||||
}
|
||||
|
||||
if (canOpenDoors && !character.LockHands)
|
||||
if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f)
|
||||
{
|
||||
CheckDoorsInPath();
|
||||
}
|
||||
@@ -311,6 +316,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
closestButton.Item.TryInteract(character, false, true, false);
|
||||
buttonPressCooldown = ButtonPressInterval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,11 @@ namespace Barotrauma
|
||||
foreach (Character potentialSpeaker in availableSpeakers)
|
||||
{
|
||||
//check if the character has an appropriate job to say the line
|
||||
if (selectedConversation.AllowedJobs.Count > 0 && !selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) continue;
|
||||
if ((potentialSpeaker.Info?.Job != null && potentialSpeaker.Info.Job.Prefab.OnlyJobSpecificDialog) ||
|
||||
selectedConversation.AllowedJobs.Count > 0)
|
||||
{
|
||||
if (!selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) continue;
|
||||
}
|
||||
|
||||
//check if the character has all required flags to say the line
|
||||
if (!ignoreFlags)
|
||||
@@ -310,6 +314,81 @@ namespace Barotrauma
|
||||
|
||||
return 1.0f - 1.0f / (index + 1);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public static void WriteToCSV()
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
NPCConversation current = list[i];
|
||||
WriteConversation(sb, current, 0);
|
||||
WriteSubConversations(sb, current.Responses, 1);
|
||||
WriteEmptyRow(sb);
|
||||
}
|
||||
|
||||
StreamWriter file = new StreamWriter(@"NPCConversations.csv");
|
||||
file.WriteLine(sb.ToString());
|
||||
file.Close();
|
||||
}
|
||||
|
||||
private static void WriteConversation(System.Text.StringBuilder sb, NPCConversation conv, int depthIndex)
|
||||
{
|
||||
sb.Append(conv.speakerIndex); // Speaker index
|
||||
sb.Append('*');
|
||||
sb.Append(depthIndex); // Depth index
|
||||
sb.Append('*');
|
||||
sb.Append(conv.Line); // Original
|
||||
sb.Append('*');
|
||||
// Translated
|
||||
sb.Append('*');
|
||||
sb.Append(string.Join(",", conv.Flags)); // Flags
|
||||
sb.Append('*');
|
||||
|
||||
for (int i = 0; i < conv.AllowedJobs.Count; i++) // Jobs
|
||||
{
|
||||
sb.Append(conv.AllowedJobs[i].Identifier);
|
||||
|
||||
if (i < conv.AllowedJobs.Count - 1)
|
||||
{
|
||||
sb.Append(",");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append('*');
|
||||
sb.Append(string.Join(",", conv.allowedSpeakerTags)); // Traits
|
||||
sb.Append('*');
|
||||
sb.Append(conv.minIntensity); // Minimum intensity
|
||||
sb.Append('*');
|
||||
sb.Append(conv.maxIntensity); // Maximum intensity
|
||||
sb.Append('*');
|
||||
// Comments
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
private static void WriteSubConversations(System.Text.StringBuilder sb, List<NPCConversation> responses, int depthIndex)
|
||||
{
|
||||
for (int i = 0; i < responses.Count; i++)
|
||||
{
|
||||
WriteConversation(sb, responses[i], depthIndex);
|
||||
|
||||
if (responses[i].Responses != null && responses[i].Responses.Count > 0)
|
||||
{
|
||||
WriteSubConversations(sb, responses[i].Responses, depthIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteEmptyRow(System.Text.StringBuilder sb)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
sb.Append('*');
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,10 +60,10 @@ namespace Barotrauma
|
||||
weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||
if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
{
|
||||
Item[] containedItems = weapon.ContainedItems;
|
||||
var containedItems = weapon.ContainedItems;
|
||||
foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
||||
{
|
||||
Item containedItem = Array.Find(containedItems, it => it != null && it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||
if (containedItem == null)
|
||||
{
|
||||
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
|
||||
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Barotrauma
|
||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||
if (containedItems == null) continue;
|
||||
|
||||
var oxygenTank = Array.Find(containedItems, it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
if (oxygenTank != null) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -60,7 +61,7 @@ namespace Barotrauma
|
||||
var containedItems = weldingTool.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
|
||||
var fuelTank = Array.Find(containedItems, i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
if (fuelTank == null)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
|
||||
|
||||
@@ -108,16 +108,20 @@ namespace Barotrauma
|
||||
//not linked to a hull -> ignore
|
||||
if (gap.linkedTo.All(l => l == null)) { continue; }
|
||||
|
||||
if (character.TeamID == 0)
|
||||
if (character.TeamID == Character.TeamType.None)
|
||||
{
|
||||
//TODO: make sure the gap isn't in another sub (outpost, respawn shuttle...?)
|
||||
if (gap.Submarine == null) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//prevent characters from attempting to fix leaks in the enemy sub
|
||||
//team 1 plays in sub 0, team 2 in sub 1
|
||||
Submarine mySub = character.TeamID < 1 || character.TeamID > Submarine.MainSubs.Length ?
|
||||
Submarine.MainSub : Submarine.MainSubs[character.TeamID - 1];
|
||||
Submarine mySub = Submarine.MainSub;
|
||||
if (character.TeamID == Character.TeamType.Team2 && Submarine.MainSubs.Length > 1)
|
||||
{
|
||||
mySub = Submarine.MainSubs[1];
|
||||
}
|
||||
|
||||
if (gap.Submarine != mySub) continue;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (Target != null && Target.Removed) return 0.0f;
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) return 0.0f;
|
||||
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
||||
if (Target != null && Target.Removed) { return 0.0f; }
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
@@ -39,15 +40,17 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Target != null && Target.Removed) return false;
|
||||
if (FollowControlledCharacter && Character.Controlled == null) { return false; }
|
||||
|
||||
if (repeat || waitUntilPathUnreachable > 0.0f) return true;
|
||||
if (Target != null && Target.Removed) { return false; }
|
||||
|
||||
if (repeat || waitUntilPathUnreachable > 0.0f) { return true; }
|
||||
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
|
||||
|
||||
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable
|
||||
if (pathSteering?.CurrentPath == null) return true;
|
||||
if (pathSteering?.CurrentPath == null) { return true; }
|
||||
|
||||
if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) return false;
|
||||
if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { return false; }
|
||||
|
||||
return !pathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
@@ -55,6 +58,8 @@ namespace Barotrauma
|
||||
|
||||
public Entity Target { get; private set; }
|
||||
|
||||
public bool FollowControlledCharacter;
|
||||
|
||||
public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
|
||||
: base (character, "")
|
||||
{
|
||||
@@ -78,12 +83,18 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (FollowControlledCharacter)
|
||||
{
|
||||
if (Character.Controlled == null) { return; }
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
|
||||
if (Target == character)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
|
||||
if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Ladder>() == null)
|
||||
|
||||
@@ -132,7 +132,8 @@ namespace Barotrauma
|
||||
{
|
||||
CloseEnough = 1.5f,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true
|
||||
IgnoreIfTargetDead = true,
|
||||
FollowControlledCharacter = orderGiver == character
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ namespace Barotrauma
|
||||
|
||||
if (!repairablesFound) { return 0.0f; }
|
||||
|
||||
float priority = 100.0f - item.Condition;
|
||||
float priority = item.Prefab.Health - item.Condition;
|
||||
//vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist =
|
||||
Math.Abs(character.WorldPosition.X - item.WorldPosition.X) +
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
//ignore items that are in full condition
|
||||
if (item.Condition >= 100.0f) continue;
|
||||
if (item.Condition >= item.Prefab.Health) continue;
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
//ignore ones that are already fixed
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace Barotrauma
|
||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in item.components)
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (!ic.HasRequiredContainedItems(addMessage: false)) continue;
|
||||
#if CLIENT
|
||||
|
||||
@@ -161,16 +161,16 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, string orderOption = "")
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "")
|
||||
{
|
||||
orderOption = orderOption ?? "";
|
||||
|
||||
string messageTag = "OrderDialog." + AITag;
|
||||
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + AITag;
|
||||
if (!string.IsNullOrEmpty(orderOption)) messageTag += "." + orderOption;
|
||||
|
||||
string msg = TextManager.Get(messageTag, true);
|
||||
if (msg == null) return "";
|
||||
|
||||
|
||||
if (targetCharacterName == null) targetCharacterName = "";
|
||||
if (targetRoomName == null) targetRoomName = "";
|
||||
return msg.Replace("[name]", targetCharacterName).Replace("[roomname]", targetRoomName);
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Barotrauma
|
||||
public AICharacter(string file, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isNetworkPlayer = false, RagdollParams ragdoll = null)
|
||||
: base(file, position, seed, characterInfo, isNetworkPlayer, ragdoll)
|
||||
{
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
@@ -50,12 +51,13 @@ namespace Barotrauma
|
||||
if (!IsRemotePlayer)
|
||||
{
|
||||
float characterDist = Vector2.DistanceSquared(cam.WorldViewCenter, WorldPosition);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
//get the distance from the closest player to this character
|
||||
foreach (Character c in CharacterList)
|
||||
{
|
||||
if (c != this && (c.IsRemotePlayer || c == GameMain.Server.Character))
|
||||
if (c != this && c.IsRemotePlayer)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(c.WorldPosition, WorldPosition);
|
||||
if (dist < characterDist)
|
||||
@@ -66,6 +68,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (characterDist > EnableSimplePhysicsDistSqr)
|
||||
{
|
||||
@@ -78,15 +81,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (IsDead || Vitality <= 0.0f || IsUnconscious || Stun > 0.0f) return;
|
||||
if (Controlled == this || !aiController.Enabled) return;
|
||||
if (!aiController.Enabled) return;
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) return;
|
||||
if (Controlled == this) return;
|
||||
|
||||
SoundUpdate(deltaTime);
|
||||
|
||||
if (!IsRemotePlayer)
|
||||
{
|
||||
aiController.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
partial void SoundUpdate(float deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,22 +131,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (Frozen) return;
|
||||
if (MainLimb == null) { return; }
|
||||
|
||||
if (character.IsDead || character.IsUnconscious || character.Stun > 0.0f)
|
||||
|
||||
if (!character.AllowInput)
|
||||
{
|
||||
Collider.Enabled = false;
|
||||
levitatingCollider = false;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
//set linear velocity even though the collider is disabled,
|
||||
//because the character won't be able to switch back from ragdoll mode until the velocity of the collider is low enough
|
||||
Collider.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
Collider.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
|
||||
}
|
||||
if (character.IsDead && deathAnimTimer < deathAnimDuration)
|
||||
{
|
||||
deathAnimTimer += deltaTime;
|
||||
UpdateDying(deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -266,6 +266,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanDrag(Character target)
|
||||
{
|
||||
return Mass / target.Mass > 0.1f;
|
||||
}
|
||||
|
||||
private float eatTimer = 0.0f;
|
||||
|
||||
public override void DragCharacter(Character target, float deltaTime)
|
||||
@@ -294,9 +299,12 @@ namespace Barotrauma
|
||||
{
|
||||
//pull the target character to the position of the mouth
|
||||
//(+ make the force fluctuate to waggle the character a bit)
|
||||
targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f));
|
||||
targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation);
|
||||
targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f));
|
||||
if (CanDrag(target))
|
||||
{
|
||||
targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f));
|
||||
targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, 20.0f);
|
||||
targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f));
|
||||
}
|
||||
|
||||
//pull the character's mouth to the target character (again with a fluctuating force)
|
||||
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
|
||||
@@ -496,7 +504,7 @@ namespace Barotrauma
|
||||
Vector2 colliderBottom = GetColliderBottom();
|
||||
|
||||
float movementAngle = 0.0f;
|
||||
float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle.Value : HeadAngle.Value) * Dir;
|
||||
float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle ?? 0 : HeadAngle ?? 0) * Dir;
|
||||
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
|
||||
{
|
||||
movementAngle += MathHelper.TwoPi;
|
||||
@@ -669,6 +677,8 @@ namespace Barotrauma
|
||||
|
||||
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
|
||||
}
|
||||
|
||||
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
|
||||
}
|
||||
|
||||
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
|
||||
@@ -707,8 +717,17 @@ namespace Barotrauma
|
||||
centerOfMass,
|
||||
new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
|
||||
true);
|
||||
l.body.PositionSmoothingFactor = 0.8f;
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SelectedCharacter != null && CanDrag(character.SelectedCharacter))
|
||||
{
|
||||
float diff = character.SelectedCharacter.SimPosition.X - centerOfMass.X;
|
||||
if (diff < 100.0f)
|
||||
{
|
||||
character.SelectedCharacter.AnimController.SetPosition(
|
||||
new Vector2(centerOfMass.X - diff, character.SelectedCharacter.SimPosition.Y), lerp: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,10 +341,13 @@ namespace Barotrauma
|
||||
if (!character.AllowInput)
|
||||
{
|
||||
levitatingCollider = false;
|
||||
Collider.Enabled = false;
|
||||
Collider.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
Collider.Enabled = false;
|
||||
Collider.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -498,7 +501,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
aiming = false;
|
||||
if (character.IsRemotePlayer && GameMain.Server == null) Collider.LinearVelocity = Vector2.Zero;
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return;
|
||||
}
|
||||
|
||||
void UpdateStanding()
|
||||
@@ -569,8 +572,11 @@ namespace Barotrauma
|
||||
movement.Y = 0.0f;
|
||||
|
||||
if (torso == null) { return; }
|
||||
|
||||
bool isNotRemote = true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
|
||||
|
||||
if (onGround && (!character.IsRemotePlayer || GameMain.Server != null))
|
||||
if (onGround && isNotRemote)
|
||||
{
|
||||
//move slower if collider isn't upright
|
||||
float rotationFactor = (float)Math.Abs(Math.Cos(Collider.Rotation));
|
||||
@@ -783,7 +789,7 @@ namespace Barotrauma
|
||||
{
|
||||
Collider.LinearVelocity = movement;
|
||||
}
|
||||
else if (onGround && (!character.IsRemotePlayer || GameMain.Server != null))
|
||||
else if (onGround && (!character.IsRemotePlayer || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)))
|
||||
{
|
||||
Collider.LinearVelocity = new Vector2(
|
||||
movement.X,
|
||||
@@ -976,7 +982,10 @@ namespace Barotrauma
|
||||
movement.Y = movement.Y - (surfaceLimiter - 1.0f) * 0.01f;
|
||||
}
|
||||
|
||||
if (!character.IsRemotePlayer || GameMain.Server != null)
|
||||
bool isNotRemote = true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
|
||||
|
||||
if (isNotRemote)
|
||||
{
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, movementLerp);
|
||||
}
|
||||
@@ -1165,7 +1174,11 @@ namespace Barotrauma
|
||||
trigger = character.SelectedConstruction.TransformTrigger(trigger);
|
||||
|
||||
bool notClimbing = false;
|
||||
if (character.IsRemotePlayer && GameMain.Server == null)
|
||||
|
||||
bool isNotRemote = true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
|
||||
|
||||
if (isNotRemote)
|
||||
{
|
||||
notClimbing = character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right);
|
||||
}
|
||||
@@ -1263,9 +1276,9 @@ namespace Barotrauma
|
||||
|
||||
bool wasCritical = target.Vitality < 0.0f;
|
||||
|
||||
if (GameMain.Client == null) //Serverside code
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
|
||||
{
|
||||
target.Oxygen += deltaTime * 0.5f; //Stabilize them
|
||||
target.Oxygen += deltaTime * 0.5f; //Stabilize them
|
||||
}
|
||||
|
||||
int skill = (int)character.GetSkillLevel("medical");
|
||||
@@ -1279,15 +1292,18 @@ namespace Barotrauma
|
||||
torso.PullJointEnabled = true;
|
||||
|
||||
//Serverside code
|
||||
if (GameMain.Client == null && target.Oxygen < -10.0f)
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
|
||||
float stabilizationAmount = skill * CPRSettings.StabilizationPerSkill;
|
||||
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.StabilizationMin, CPRSettings.StabilizationMax);
|
||||
character.Oxygen -= (1.0f / stabilizationAmount) * deltaTime; //Worse skill = more oxygen required
|
||||
if (character.Oxygen > 0.0f) target.Oxygen += stabilizationAmount * deltaTime; //we didn't suffocate yet did we
|
||||
if (target.Oxygen < -10.0f)
|
||||
{
|
||||
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
|
||||
float stabilizationAmount = skill * CPRSettings.StabilizationPerSkill;
|
||||
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.StabilizationMin, CPRSettings.StabilizationMax);
|
||||
character.Oxygen -= (1.0f / stabilizationAmount) * deltaTime; //Worse skill = more oxygen required
|
||||
if (character.Oxygen > 0.0f) target.Oxygen += stabilizationAmount * deltaTime; //we didn't suffocate yet did we
|
||||
|
||||
//DebugConsole.NewMessage("CPR Us: " + character.Oxygen + " Them: " + target.Oxygen + " How good we are: restore " + cpr + " use " + (30.0f - cpr), Color.Aqua);
|
||||
//DebugConsole.NewMessage("CPR Us: " + character.Oxygen + " Them: " + target.Oxygen + " How good we are: restore " + cpr + " use " + (30.0f - cpr), Color.Aqua);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1319,7 +1335,7 @@ namespace Barotrauma
|
||||
},
|
||||
0.0f, true, 0.0f, character);
|
||||
}
|
||||
if (GameMain.Client == null) //Serverside code
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
|
||||
{
|
||||
float reviveChance = skill * CPRSettings.ReviveChancePerSkill;
|
||||
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.ReviveChanceExponent);
|
||||
@@ -1330,7 +1346,7 @@ namespace Barotrauma
|
||||
//increase oxygen and clamp it above zero
|
||||
// -> the character should be revived if there are no major afflictions in addition to lack of oxygen
|
||||
target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cprPump += deltaTime;
|
||||
@@ -1380,7 +1396,7 @@ namespace Barotrauma
|
||||
if (Anim == Animation.Climbing)
|
||||
{
|
||||
//cannot drag up ladders if the character is conscious
|
||||
if (target.AllowInput && GameMain.Client == null)
|
||||
if (target.AllowInput && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
return;
|
||||
@@ -1459,7 +1475,7 @@ namespace Barotrauma
|
||||
|
||||
Limb pullLimb = i == 0 ? leftHand : rightHand;
|
||||
|
||||
if (GameMain.Client == null)
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//stop dragging if there's something between the pull limb and the target limb
|
||||
Vector2 sourceSimPos = pullLimb.SimPosition;
|
||||
@@ -1541,7 +1557,7 @@ namespace Barotrauma
|
||||
|
||||
float dist = ConvertUnits.ToSimUnits(Vector2.Distance(target.WorldPosition, WorldPosition));
|
||||
//let the target break free if it's moving away and gets far enough
|
||||
if (GameMain.Client == null && dist > 1.4f && target.AllowInput &&
|
||||
if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && dist > 1.4f && target.AllowInput &&
|
||||
Vector2.Dot(target.WorldPosition - WorldPosition, target.AnimController.TargetMovement) > 0)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
@@ -1609,7 +1625,7 @@ namespace Barotrauma
|
||||
|
||||
if (Anim != Animation.Climbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero)
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
|
||||
|
||||
Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX;
|
||||
|
||||
@@ -1872,15 +1888,16 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < character.SelectedItems.Length; i++)
|
||||
{
|
||||
if (character.SelectedItems[i] != null && character.SelectedItems[i].body != null)
|
||||
if (i == 1 && character.SelectedItems[0] == character.SelectedItems[1])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (character.SelectedItems[i]?.body != null && !character.SelectedItems[i].Removed)
|
||||
{
|
||||
difference = character.SelectedItems[i].body.SimPosition - torso.SimPosition;
|
||||
difference = Vector2.Transform(difference, torsoTransform);
|
||||
difference.Y = -difference.Y;
|
||||
|
||||
character.SelectedItems[i].body.SetTransform(
|
||||
torso.SimPosition + Vector2.Transform(difference, -torsoTransform),
|
||||
MathUtils.WrapAngleTwoPi(-character.SelectedItems[i].body.Rotation));
|
||||
character.SelectedItems[i].body.SimPosition,
|
||||
MathUtils.WrapAngleTwoPi(character.SelectedItems[i].body.Rotation + MathHelper.Pi));
|
||||
character.SelectedItems[i].GetComponent<Holdable>()?.Flip();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,7 +1915,6 @@ namespace Barotrauma
|
||||
case LimbType.RightHand:
|
||||
case LimbType.RightArm:
|
||||
case LimbType.RightForearm:
|
||||
mirror = true;
|
||||
flipAngle = true;
|
||||
break;
|
||||
case LimbType.LeftThigh:
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class HumanoidAnimParams : ISerializableEntity
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public HumanoidAnimParams(string file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
Name = doc.Root.Name.ToString();
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc.Root);
|
||||
}
|
||||
|
||||
[Serialize(0.3f, true), Editable]
|
||||
public float GetUpSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.54f, true), Editable]
|
||||
public float HeadPosition
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.15f, true), Editable]
|
||||
public float TorsoPosition
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.25f, true), Editable]
|
||||
public float HeadLeanAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.25f, true), Editable]
|
||||
public float TorsoLeanAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(5.0f, true), Editable]
|
||||
public float CycleSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(15.0f, true), Editable]
|
||||
public float FootMoveStrength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize(20.0f, true), Editable]
|
||||
public float FootRotateStrength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0.4,0.12", true), Editable]
|
||||
public Vector2 StepSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0.0, 0.0", true), Editable]
|
||||
public Vector2 FootMoveOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(10.0f, true), Editable]
|
||||
public float LegCorrectionTorque
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(15.0f, true), Editable]
|
||||
public float ThighCorrectionTorque
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0.4, 0.15", true), Editable]
|
||||
public Vector2 HandMoveAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("-0.15, 0.0", true), Editable]
|
||||
public Vector2 HandMoveOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.7f, true), Editable]
|
||||
public float HandMoveStrength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1.0f, true), Editable]
|
||||
public float HandClampY
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -97,7 +97,7 @@ namespace Barotrauma
|
||||
|
||||
public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType.ToString()}";
|
||||
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/";
|
||||
public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetDefaultFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml";
|
||||
public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml";
|
||||
|
||||
protected static string GetFolder(string speciesName)
|
||||
{
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
|
||||
public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll";
|
||||
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Ragdolls/";
|
||||
public static string GetDefaultFile(string speciesName) => $"{GetDefaultFolder(speciesName)}{GetDefaultFileName(speciesName)}.xml";
|
||||
public static string GetDefaultFile(string speciesName) => $"{GetFolder(speciesName)}{GetDefaultFileName(speciesName)}.xml";
|
||||
|
||||
private static readonly object[] dummyParams = new object[]
|
||||
{
|
||||
@@ -662,7 +662,7 @@ namespace Barotrauma
|
||||
public SerializableEntityEditor SerializableEntityEditor { get; protected set; }
|
||||
public virtual void AddToEditor(ParamsEditor editor)
|
||||
{
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, false, true);
|
||||
SerializableEntityEditor = new SerializableEntityEditor(editor.EditorBox.Content.RectTransform, this, inGame: false, showName: true);
|
||||
SubParams.ForEach(sp => sp.AddToEditor(editor));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -314,16 +314,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (!PhysicsBody.IsValidShape(limbParams.Radius, limbParams.Height, limbParams.Width))
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot create the ragdoll: invalid collider dimensions on limb: " + limbParams.Name);
|
||||
return;
|
||||
DebugConsole.ThrowError($"Invalid collider dimensions (r: {limbParams.Radius}, h: {limbParams.Height}, w: {limbParams.Width}) on limb: {limbParams.Name}. Fixing.");
|
||||
limbParams.Radius = 10;
|
||||
}
|
||||
}
|
||||
foreach (var colliderParams in RagdollParams.ColliderParams)
|
||||
{
|
||||
if (!PhysicsBody.IsValidShape(colliderParams.Radius, colliderParams.Height, colliderParams.Width))
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot create the ragdoll: invalid collider dimensions on collider: " + colliderParams.Name);
|
||||
return;
|
||||
DebugConsole.ThrowError($"Invalid collider dimensions (r: {colliderParams.Radius}, h: {colliderParams.Height}, w: {colliderParams.Width}) on collider: {colliderParams.Name}. Fixing.");
|
||||
colliderParams.Radius = 10;
|
||||
}
|
||||
}
|
||||
CreateColliders();
|
||||
@@ -335,11 +335,21 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var kvp in items)
|
||||
{
|
||||
var limb = limbs[kvp.Key.ID];
|
||||
int id = kvp.Key.ID;
|
||||
// This can be the case if we manipulate the ragdoll in runtime (husk appendage, limb severance)
|
||||
if (id > limbs.Length - 1) { continue; }
|
||||
var limb = limbs[id];
|
||||
var itemList = kvp.Value;
|
||||
limb.WearingItems.AddRange(itemList);
|
||||
}
|
||||
}
|
||||
if (character.SpeciesName.ToLowerInvariant() == "humanhusk")
|
||||
{
|
||||
if (Limbs.None(l => l.Name.ToLowerInvariant() == "huskappendage"))
|
||||
{
|
||||
AfflictionHusk.AttachHuskAppendage(character, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
|
||||
@@ -431,12 +441,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void SetupDrawOrder();
|
||||
|
||||
/// <summary>
|
||||
/// Inversed draw order, which is used for drawing the limbs in 3d (deformable sprites).
|
||||
/// </summary>
|
||||
protected Limb[] inversedLimbDrawOrder;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Saves all serializable data in the currently selected ragdoll params. This method should properly handle character flipping.
|
||||
/// </summary>
|
||||
@@ -495,9 +500,7 @@ namespace Barotrauma
|
||||
|
||||
public void AddJoint(JointParams jointParams)
|
||||
{
|
||||
byte limb1ID = Convert.ToByte(jointParams.Limb1);
|
||||
byte limb2ID = Convert.ToByte(jointParams.Limb2);
|
||||
LimbJoint joint = new LimbJoint(Limbs[limb1ID], Limbs[limb2ID], jointParams, this);
|
||||
LimbJoint joint = new LimbJoint(Limbs[jointParams.Limb1], Limbs[jointParams.Limb2], jointParams, this);
|
||||
GameMain.World.AddJoint(joint);
|
||||
for (int i = 0; i < LimbJoints.Length; i++)
|
||||
{
|
||||
@@ -509,11 +512,6 @@ namespace Barotrauma
|
||||
LimbJoints[LimbJoints.Length - 1] = joint;
|
||||
}
|
||||
|
||||
public void AddJoint(XElement subElement, float scale = 1.0f)
|
||||
{
|
||||
AddJoint(new JointParams(subElement, RagdollParams));
|
||||
}
|
||||
|
||||
protected void AddLimb(LimbParams limbParams)
|
||||
{
|
||||
byte ID = Convert.ToByte(limbParams.ID);
|
||||
@@ -591,7 +589,6 @@ namespace Barotrauma
|
||||
|
||||
public bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
Structure structure = f2.Body.UserData as Structure;
|
||||
|
||||
if (f2.Body.UserData is Submarine && character.Submarine == (Submarine)f2.Body.UserData) return false;
|
||||
|
||||
@@ -599,7 +596,7 @@ namespace Barotrauma
|
||||
if (f2.Body.UserData as string == "blocker" && f2.Body != outsideCollisionBlocker) return false;
|
||||
|
||||
//always collides with bodies other than structures
|
||||
if (structure == null)
|
||||
if (!(f2.Body.UserData is Structure structure))
|
||||
{
|
||||
CalculateImpact(f1, f2, contact);
|
||||
return true;
|
||||
@@ -635,8 +632,7 @@ namespace Barotrauma
|
||||
if (contact.Manifold.LocalNormal.Y < 0.0f) return false;
|
||||
|
||||
//4. contact points is above the bottom half of the collider
|
||||
Vector2 normal; FarseerPhysics.Common.FixedArray2<Vector2> points;
|
||||
contact.GetWorldManifold(out normal, out points);
|
||||
contact.GetWorldManifold(out Vector2 normal, out FarseerPhysics.Common.FixedArray2<Vector2> points);
|
||||
if (points[0].Y > Collider.SimPosition.Y) return false;
|
||||
|
||||
//5. in water
|
||||
@@ -671,7 +667,10 @@ namespace Barotrauma
|
||||
float impact = Vector2.Dot(velocity, -normal);
|
||||
if (f1.Body == Collider.FarseerBody)
|
||||
{
|
||||
if (!character.IsRemotePlayer || GameMain.Server != null)
|
||||
bool isNotRemote = true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
|
||||
|
||||
if (isNotRemote)
|
||||
{
|
||||
if (impact > ImpactTolerance)
|
||||
{
|
||||
@@ -689,7 +688,7 @@ namespace Barotrauma
|
||||
|
||||
ImpactProjSpecific(impact, f1.Body);
|
||||
}
|
||||
|
||||
|
||||
public void SeverLimbJoint(LimbJoint limbJoint)
|
||||
{
|
||||
if (!limbJoint.CanBeSevered || limbJoint.IsSevered)
|
||||
@@ -713,9 +712,9 @@ namespace Barotrauma
|
||||
|
||||
SeverLimbJointProjSpecific(limbJoint);
|
||||
|
||||
if (GameMain.Server != null)
|
||||
if (GameMain.NetworkMember!=null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.Status });
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,6 +984,12 @@ namespace Barotrauma
|
||||
|
||||
SetPosition(Collider.SimPosition + moveAmount);
|
||||
character.CursorPosition += moveAmount;
|
||||
|
||||
Collider?.UpdateDrawPosition();
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
limb.body.UpdateDrawPosition();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCollisionCategories()
|
||||
@@ -1025,6 +1030,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (!character.Enabled || Frozen) return;
|
||||
|
||||
CheckValidity();
|
||||
|
||||
UpdateNetPlayerPosition(deltaTime);
|
||||
CheckDistFromCollider();
|
||||
UpdateCollisionCategories();
|
||||
@@ -1158,10 +1165,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (contacts.Contact.Enabled && contacts.Contact.IsTouching)
|
||||
{
|
||||
Vector2 normal;
|
||||
FarseerPhysics.Common.FixedArray2<Vector2> points;
|
||||
|
||||
contacts.Contact.GetWorldManifold(out normal, out points);
|
||||
contacts.Contact.GetWorldManifold(out Vector2 normal, out FarseerPhysics.Common.FixedArray2<Vector2> points);
|
||||
|
||||
switch (contacts.Contact.FixtureA.CollisionCategories)
|
||||
{
|
||||
@@ -1288,6 +1292,55 @@ namespace Barotrauma
|
||||
UpdateProjSpecific(deltaTime);
|
||||
}
|
||||
|
||||
private void CheckValidity()
|
||||
{
|
||||
CheckValidity(Collider);
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
CheckValidity(limb.body);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckValidity(PhysicsBody body)
|
||||
{
|
||||
string errorMsg = null;
|
||||
string bodyName = body.UserData is Limb ? "Limb" : "Collider";
|
||||
if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f)
|
||||
{
|
||||
errorMsg = bodyName+ " position invalid (" + body.SimPosition + ", character: " + character.Name + "), resetting the ragdoll.";
|
||||
}
|
||||
else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f)
|
||||
{
|
||||
errorMsg = bodyName + " velocity invalid (" + body.LinearVelocity + ", character: " + character.Name + "), resetting the ragdoll.";
|
||||
}
|
||||
else if (!MathUtils.IsValid(body.Rotation))
|
||||
{
|
||||
errorMsg = bodyName + " rotation invalid (" + body.Rotation + ", character: " + character.Name + "), resetting the ragdoll.";
|
||||
}
|
||||
else if (!MathUtils.IsValid(body.AngularVelocity) || Math.Abs(body.AngularVelocity) > 1000f)
|
||||
{
|
||||
errorMsg = bodyName + " angular velocity invalid (" + body.AngularVelocity + ", character: " + character.Name + "), resetting the ragdoll.";
|
||||
}
|
||||
if (errorMsg != null)
|
||||
{
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.CheckValidity:" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
|
||||
if (!MathUtils.IsValid(Collider.SimPosition) || Math.Abs(Collider.SimPosition.X) > 1e10f || Math.Abs(Collider.SimPosition.Y) > 1e10f)
|
||||
{
|
||||
Collider.SetTransform(Vector2.Zero, 0.0f);
|
||||
}
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
limb.body.SetTransform(Collider.SimPosition, 0.0f);
|
||||
limb.body.ResetDynamics();
|
||||
}
|
||||
SetInitialLimbPositions();
|
||||
return;
|
||||
}
|
||||
UpdateProjSpecific(deltaTime);
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
partial void Splash(Limb limb, Hull limbHull);
|
||||
@@ -1365,7 +1418,15 @@ namespace Barotrauma
|
||||
|
||||
Vector2 limbMoveAmount = simPosition - MainLimb.SimPosition;
|
||||
|
||||
Collider.SetTransform(simPosition, Collider.Rotation);
|
||||
if (lerp)
|
||||
{
|
||||
Collider.TargetPosition = simPosition;
|
||||
Collider.MoveToTargetPosition(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.SetTransform(simPosition, Collider.Rotation);
|
||||
}
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
@@ -1445,7 +1506,8 @@ namespace Barotrauma
|
||||
prevCollisionCategory = Category.None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos);
|
||||
private void UpdateNetPlayerPosition(float deltaTime)
|
||||
{
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
@@ -1467,214 +1529,8 @@ namespace Barotrauma
|
||||
character.MemState[i].TransformOutToInside(currentHull.Submarine);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Server != null) return; //the server should not be trying to correct any positions, it's authoritative
|
||||
|
||||
if (character != GameMain.NetworkMember.Character || !character.AllowInput)
|
||||
{
|
||||
//remove states without a timestamp (there may still be ID-based states
|
||||
//in the list when the controlled character switches to timestamp-based interpolation)
|
||||
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
|
||||
|
||||
//use simple interpolation for other players' characters and characters that can't move
|
||||
if (character.MemState.Count > 0)
|
||||
{
|
||||
CharacterStateInfo serverPos = character.MemState.Last();
|
||||
if (!character.isSynced)
|
||||
{
|
||||
SetPosition(serverPos.Position, false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (character.MemState[0].Interact == null || character.MemState[0].Interact.Removed)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
else if (character.MemState[0].Interact is Character)
|
||||
{
|
||||
character.SelectCharacter((Character)character.MemState[0].Interact);
|
||||
}
|
||||
else if (character.MemState[0].Interact is Item newSelectedConstruction)
|
||||
{
|
||||
if (newSelectedConstruction != null && character.SelectedConstruction != newSelectedConstruction)
|
||||
{
|
||||
foreach (var ic in newSelectedConstruction.components)
|
||||
{
|
||||
if (ic.CanBeSelected) ic.Select(character);
|
||||
}
|
||||
}
|
||||
character.SelectedConstruction = newSelectedConstruction;
|
||||
}
|
||||
|
||||
if (character.MemState[0].Animation == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
}
|
||||
|
||||
Vector2 newVelocity = Vector2.Zero;
|
||||
Vector2 newPosition = Collider.SimPosition;
|
||||
Collider.CorrectPosition(character.MemState, deltaTime, out newVelocity, out newPosition);
|
||||
|
||||
newVelocity = newVelocity.ClampLength(100.0f);
|
||||
if (!MathUtils.IsValid(newVelocity)) newVelocity = Vector2.Zero;
|
||||
overrideTargetMovement = newVelocity;
|
||||
Collider.LinearVelocity = newVelocity;
|
||||
|
||||
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
|
||||
if (distSqrd > 10.0f)
|
||||
{
|
||||
SetPosition(newPosition);
|
||||
}
|
||||
else if (distSqrd > 0.01f)
|
||||
{
|
||||
Collider.SetTransform(newPosition, Collider.Rotation);
|
||||
}
|
||||
|
||||
//unconscious/dead characters can't correct their position using AnimController movement
|
||||
// -> we need to correct it manually
|
||||
if (!character.AllowInput)
|
||||
{
|
||||
Collider.LinearVelocity = overrideTargetMovement;
|
||||
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.PullJointEnabled = true;
|
||||
}
|
||||
}
|
||||
character.MemLocalState.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
//remove states with a timestamp (there may still timestamp-based states
|
||||
//in the list if the controlled character switches from timestamp-based interpolation to ID-based)
|
||||
character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
|
||||
|
||||
for (int i = 0; i < character.MemLocalState.Count; i++)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
//transform in-sub coordinates to outside coordinates
|
||||
if (character.MemLocalState[i].Position.Y > lowestSubPos)
|
||||
{
|
||||
character.MemLocalState[i].TransformInToOutside();
|
||||
}
|
||||
}
|
||||
else if (currentHull?.Submarine != null)
|
||||
{
|
||||
//transform outside coordinates to in-sub coordinates
|
||||
if (character.MemLocalState[i].Position.Y < lowestSubPos)
|
||||
{
|
||||
character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.MemState.Count < 1) return;
|
||||
|
||||
overrideTargetMovement = Vector2.Zero;
|
||||
|
||||
CharacterStateInfo serverPos = character.MemState.Last();
|
||||
|
||||
if (!character.isSynced)
|
||||
{
|
||||
SetPosition(serverPos.Position, false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID);
|
||||
if (localPosIndex > -1)
|
||||
{
|
||||
CharacterStateInfo localPos = character.MemLocalState[localPosIndex];
|
||||
|
||||
//the entity we're interacting with doesn't match the server's
|
||||
if (localPos.Interact != serverPos.Interact)
|
||||
{
|
||||
if (serverPos.Interact == null || serverPos.Interact.Removed)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
else if (serverPos.Interact is Character)
|
||||
{
|
||||
character.SelectCharacter((Character)serverPos.Interact);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newSelectedConstruction = (Item)serverPos.Interact;
|
||||
if (newSelectedConstruction != null && character.SelectedConstruction != newSelectedConstruction)
|
||||
{
|
||||
newSelectedConstruction.TryInteract(character, true, true);
|
||||
}
|
||||
character.SelectedConstruction = newSelectedConstruction;
|
||||
}
|
||||
}
|
||||
|
||||
if (localPos.Animation != serverPos.Animation)
|
||||
{
|
||||
if (serverPos.Animation == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
}
|
||||
}
|
||||
|
||||
Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos);
|
||||
Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos);
|
||||
|
||||
if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine)
|
||||
{
|
||||
//hull subs don't match => teleport the camera to the other sub
|
||||
character.Submarine = serverHull.Submarine;
|
||||
character.CurrentHull = currentHull = serverHull;
|
||||
SetPosition(serverPos.Position);
|
||||
character.MemLocalState.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 positionError = serverPos.Position - localPos.Position;
|
||||
float rotationError = serverPos.Rotation - localPos.Rotation;
|
||||
|
||||
for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
|
||||
{
|
||||
Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos);
|
||||
if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine))) break;
|
||||
character.MemLocalState[i].Translate(positionError, rotationError);
|
||||
}
|
||||
|
||||
float errorMagnitude = positionError.Length();
|
||||
if (errorMagnitude > 0.01f)
|
||||
{
|
||||
Collider.SetTransform(Collider.SimPosition + positionError, Collider.Rotation + rotationError);
|
||||
if (errorMagnitude > 0.5f)
|
||||
{
|
||||
character.MemLocalState.Clear();
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
limb.body.SetTransform(limb.body.SimPosition + positionError, limb.body.Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
|
||||
character.MemState.Clear();
|
||||
}
|
||||
UpdateNetPlayerPositionProjSpecific(deltaTime, lowestSubPos);
|
||||
}
|
||||
|
||||
private Vector2 GetFlowForce()
|
||||
@@ -1735,11 +1591,8 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < Collider.FarseerBody.FixtureList.Count; i++)
|
||||
{
|
||||
FarseerPhysics.Collision.AABB aabb;
|
||||
FarseerPhysics.Common.Transform transform;
|
||||
|
||||
Collider.FarseerBody.GetTransform(out transform);
|
||||
Collider.FarseerBody.FixtureList[i].Shape.ComputeAABB(out aabb, ref transform, i);
|
||||
Collider.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
Collider.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
|
||||
|
||||
lowestBound = Math.Min(aabb.LowerBound.Y, lowestBound);
|
||||
}
|
||||
|
||||
@@ -356,6 +356,16 @@ namespace Barotrauma
|
||||
effect.Apply(effectType, deltaTime, (Character)target, ((Character)target).AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
}
|
||||
if (target is Entity entity)
|
||||
{
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, deltaTime, entity, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attackResult;
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Barotrauma
|
||||
{
|
||||
public static List<Character> CharacterList = new List<Character>();
|
||||
|
||||
partial void UpdateLimbLightSource(Limb limb);
|
||||
|
||||
private bool enabled = true;
|
||||
public bool Enabled
|
||||
{
|
||||
@@ -42,12 +44,7 @@ namespace Barotrauma
|
||||
{
|
||||
limb.body.Enabled = enabled;
|
||||
}
|
||||
#if CLIENT
|
||||
if (limb.LightSource != null)
|
||||
{
|
||||
limb.LightSource.Enabled = enabled;
|
||||
}
|
||||
#endif
|
||||
UpdateLimbLightSource(limb);
|
||||
}
|
||||
AnimController.Collider.Enabled = value;
|
||||
}
|
||||
@@ -71,8 +68,16 @@ namespace Barotrauma
|
||||
protected Key[] keys;
|
||||
private Item[] selectedItems;
|
||||
|
||||
private byte teamID;
|
||||
public byte TeamID
|
||||
public enum TeamType
|
||||
{
|
||||
None,
|
||||
Team1,
|
||||
Team2,
|
||||
FriendlyNPC
|
||||
}
|
||||
|
||||
private TeamType teamID;
|
||||
public TeamType TeamID
|
||||
{
|
||||
get { return teamID; }
|
||||
set
|
||||
@@ -111,6 +116,9 @@ namespace Barotrauma
|
||||
|
||||
private string currentOrderOption;
|
||||
|
||||
private List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
private List<float> speedMultipliers = new List<float>();
|
||||
|
||||
private List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
public Entity ViewTarget
|
||||
@@ -159,15 +167,22 @@ namespace Barotrauma
|
||||
return info != null && !string.IsNullOrWhiteSpace(info.Name) ? info.Name : SpeciesName;
|
||||
}
|
||||
}
|
||||
|
||||
private string displayName;
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
return displayName != null && displayName.Length > 0 ? displayName : Name;
|
||||
}
|
||||
}
|
||||
|
||||
//Only used by server logs to determine "true identity" of the player for cases when they're disguised
|
||||
public string LogName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.Server != null && !GameMain.Server.AllowDisguises) return Name;
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !GameMain.Client.AllowDisguises) return Name;
|
||||
#endif
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowDisguises) return Name;
|
||||
return info != null && !string.IsNullOrWhiteSpace(info.Name) ? info.Name + (info.DisplayName != info.Name ? " (as " + info.DisplayName + ")" : "") : SpeciesName;
|
||||
}
|
||||
}
|
||||
@@ -240,6 +255,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 SmoothedCursorPosition
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 CursorWorldPosition
|
||||
{
|
||||
get { return Submarine == null ? cursorPosition : cursorPosition + Submarine.Position; }
|
||||
@@ -345,7 +366,7 @@ namespace Barotrauma
|
||||
get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; }
|
||||
set
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
|
||||
SetStun(value, true);
|
||||
}
|
||||
@@ -435,16 +456,7 @@ namespace Barotrauma
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify the character's speed via StatusEffects
|
||||
/// </summary>
|
||||
public float SpeedMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public Item[] SelectedItems
|
||||
{
|
||||
@@ -517,9 +529,64 @@ namespace Barotrauma
|
||||
set { canInventoryBeAccessed = value; }
|
||||
}
|
||||
|
||||
private bool canBeDragged = true;
|
||||
public bool CanBeDragged
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!canBeDragged) { return false; }
|
||||
if (Removed || !AnimController.Draggable) { return false; }
|
||||
return IsDead || Stun > 0.0f || LockHands || IsUnconscious;
|
||||
}
|
||||
set { canBeDragged = value; }
|
||||
}
|
||||
|
||||
//can other characters access the inventory of this character
|
||||
private bool canInventoryBeAccessed = true;
|
||||
public bool CanInventoryBeAccessed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!canInventoryBeAccessed || Removed || Inventory == null) { return false; }
|
||||
if (!Inventory.AccessibleWhenAlive)
|
||||
{
|
||||
return IsDead;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (IsDead || Stun > 0.0f || LockHands || IsUnconscious);
|
||||
}
|
||||
}
|
||||
set { canInventoryBeAccessed = value; }
|
||||
}
|
||||
|
||||
public override Vector2 SimPosition
|
||||
{
|
||||
get { return AnimController.Collider.SimPosition; }
|
||||
get
|
||||
{
|
||||
if (AnimController?.Collider == null)
|
||||
{
|
||||
string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed+".";
|
||||
if (AnimController == null)
|
||||
{
|
||||
errorMsg += " AnimController == null";
|
||||
}
|
||||
else if (AnimController.Collider == null)
|
||||
{
|
||||
errorMsg += " AnimController.Collider == null";
|
||||
}
|
||||
|
||||
DebugConsole.NewMessage(errorMsg, Color.Red);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Character.SimPosition:AccessRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
errorMsg + "\n" + Environment.StackTrace);
|
||||
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
return AnimController.Collider.SimPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 Position
|
||||
@@ -550,7 +617,7 @@ namespace Barotrauma
|
||||
/// <param name="seed">RNG seed to use if the character config has randomizable parameters.</param>
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select randomly.</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null)
|
||||
{
|
||||
return Create(characterInfo.File, position, seed, characterInfo, isRemotePlayer, hasAi, true, ragdoll);
|
||||
@@ -566,7 +633,7 @@ namespace Barotrauma
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="createNetworkEvent">Should clients receive a network event about the creation of this character?</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select randomly.</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(string file, Vector2 position, string seed, CharacterInfo characterInfo = null, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null)
|
||||
{
|
||||
#if LINUX
|
||||
@@ -596,7 +663,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
Character newCharacter = null;
|
||||
if (file.ToLower() != HumanConfigFile?.ToLower())
|
||||
if (file != HumanConfigFile)
|
||||
{
|
||||
var aiCharacter = new AICharacter(file, position, seed, characterInfo, isRemotePlayer, ragdoll);
|
||||
var ai = new EnemyAIController(aiCharacter, file, seed);
|
||||
@@ -622,16 +689,12 @@ namespace Barotrauma
|
||||
//newCharacter.minVitality = -100.0f;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Spawner != null && createNetworkEvent)
|
||||
{
|
||||
Spawner.CreateNetworkEvent(newCharacter, false);
|
||||
}
|
||||
|
||||
if (characterInfo != null)
|
||||
{
|
||||
newCharacter.LoadHeadAttachments();
|
||||
}
|
||||
|
||||
#endif
|
||||
return newCharacter;
|
||||
}
|
||||
|
||||
@@ -654,17 +717,27 @@ namespace Barotrauma
|
||||
Properties = SerializableProperty.GetProperties(this);
|
||||
|
||||
Info = characterInfo;
|
||||
if (file == humanConfigFile && characterInfo == null)
|
||||
if (file == HumanConfigFile || file == GetConfigFile("humanhusk"))
|
||||
{
|
||||
Info = new CharacterInfo(file);
|
||||
if (characterInfo == null)
|
||||
{
|
||||
Info = new CharacterInfo(HumanConfigFile);
|
||||
}
|
||||
}
|
||||
|
||||
keys = new Key[Enum.GetNames(typeof(InputType)).Length];
|
||||
for (int i = 0; i < Enum.GetNames(typeof(InputType)).Length; i++)
|
||||
{
|
||||
keys[i] = new Key((InputType)i);
|
||||
}
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
InitProjSpecific(doc);
|
||||
SpeciesName = doc.Root.GetAttributeString("name", "Unknown");
|
||||
displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true);
|
||||
|
||||
SpeciesName = doc.Root.GetAttributeString("name", "Unknown");
|
||||
IsHumanoid = doc.Root.GetAttributeBool("humanoid", false);
|
||||
canSpeak = doc.Root.GetAttributeBool("canspeak", false);
|
||||
needsAir = doc.Root.GetAttributeBool("needsair", false);
|
||||
@@ -744,6 +817,11 @@ namespace Barotrauma
|
||||
// - server receives an input message from the client controlling the character
|
||||
// - if an AICharacter, the server enables it when close enough to any of the players
|
||||
Enabled = GameMain.NetworkMember == null;
|
||||
|
||||
if (Info != null)
|
||||
{
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
}
|
||||
partial void InitProjSpecific(XDocument doc);
|
||||
|
||||
@@ -754,13 +832,7 @@ namespace Barotrauma
|
||||
if (head == null) { return; }
|
||||
if (headId.HasValue)
|
||||
{
|
||||
Info.Head.HeadSpriteId = headId.Value;
|
||||
Info.LoadHeadSprite();
|
||||
Info.HairIndex = hairIndex ?? -1;
|
||||
Info.BeardIndex = beardIndex ?? -1;
|
||||
Info.MoustacheIndex = moustacheIndex ?? -1;
|
||||
Info.FaceAttachmentIndex = faceAttachmentIndex ?? -1;
|
||||
Info.LoadHeadAttachments();
|
||||
Info.RecreateHead(headId.Value, Info.Race, Info.Gender, hairIndex ?? -1, beardIndex ?? -1, moustacheIndex ?? -1, faceAttachmentIndex ?? -1);
|
||||
}
|
||||
#if CLIENT
|
||||
head.RecreateSprite();
|
||||
@@ -816,7 +888,7 @@ namespace Barotrauma
|
||||
|
||||
public static string GetConfigFile(string speciesName)
|
||||
{
|
||||
string configFile = CharacterConfigFiles.FirstOrDefault(c => Path.GetFileName(c) == $"{speciesName}.xml");
|
||||
string configFile = CharacterConfigFiles.FirstOrDefault(c => Path.GetFileName(c).ToLowerInvariant() == $"{speciesName.ToLowerInvariant()}.xml");
|
||||
if (configFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't find a config file for {speciesName} from the selected content packages!");
|
||||
@@ -828,7 +900,8 @@ namespace Barotrauma
|
||||
|
||||
public bool IsKeyHit(InputType inputType)
|
||||
{
|
||||
if (GameMain.Server != null && Controlled != this)
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && IsRemotePlayer)
|
||||
{
|
||||
switch (inputType)
|
||||
{
|
||||
@@ -858,13 +931,15 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return keys[(int)inputType].Hit;
|
||||
}
|
||||
|
||||
public bool IsKeyDown(InputType inputType)
|
||||
{
|
||||
if (GameMain.Server != null && Character.Controlled != this)
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && IsRemotePlayer)
|
||||
{
|
||||
switch (inputType)
|
||||
{
|
||||
@@ -893,6 +968,7 @@ namespace Barotrauma
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return keys[(int)inputType].Held;
|
||||
}
|
||||
@@ -901,6 +977,7 @@ namespace Barotrauma
|
||||
{
|
||||
keys[(int)inputType].Hit = hit;
|
||||
keys[(int)inputType].Held = held;
|
||||
keys[(int)inputType].SetState(hit, held);
|
||||
}
|
||||
|
||||
public void ClearInput(InputType inputType)
|
||||
@@ -935,8 +1012,10 @@ namespace Barotrauma
|
||||
{
|
||||
return (Info == null || Info.Job == null) ? 0.0f : Info.Job.GetSkillLevel(skillIdentifier);
|
||||
}
|
||||
|
||||
float findFocusedTimer;
|
||||
|
||||
// TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner
|
||||
public Vector2? OverrideMovement { get; set; }
|
||||
public bool ForceRun { get; set; }
|
||||
|
||||
// TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner
|
||||
public Vector2? OverrideMovement { get; set; }
|
||||
@@ -985,13 +1064,59 @@ namespace Barotrauma
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
if (run || SpeedMultiplier <= 0.0f) targetMovement *= SpeedMultiplier;
|
||||
float speedMultiplier = SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
|
||||
SpeedMultiplier = 1; // Reset, items will set the value before the next update
|
||||
ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
|
||||
return targetMovement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify the character's speed via StatusEffects
|
||||
/// </summary>
|
||||
public float SpeedMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (speedMultipliers.Count == 0) return 1f;
|
||||
|
||||
float greatestPositive = 1f;
|
||||
float greatestNegative = 1f;
|
||||
|
||||
for (int i = 0; i < speedMultipliers.Count; i++)
|
||||
{
|
||||
float val = speedMultipliers[i];
|
||||
if (val < 1f)
|
||||
{
|
||||
if (val < greatestNegative)
|
||||
{
|
||||
greatestNegative = val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val > greatestPositive)
|
||||
{
|
||||
greatestPositive = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return greatestPositive - (1f - greatestNegative);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == 1f) return;
|
||||
speedMultipliers.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetSpeedMultiplier()
|
||||
{
|
||||
speedMultipliers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies temporary limits to the speed (damage).
|
||||
/// </summary>
|
||||
@@ -1024,12 +1149,23 @@ namespace Barotrauma
|
||||
ViewTarget = null;
|
||||
if (!AllowInput) return;
|
||||
|
||||
if (!(this is AICharacter) || controlled == this || IsRemotePlayer)
|
||||
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
|
||||
if (Controlled == this)
|
||||
{
|
||||
SmoothedCursorPosition = cursorPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff);
|
||||
SmoothedCursorPosition = cursorPosition - smoothedCursorDiff;
|
||||
}
|
||||
|
||||
if (!(this is AICharacter) || Controlled == this || IsRemotePlayer)
|
||||
{
|
||||
Vector2 targetMovement = GetTargetMovement();
|
||||
|
||||
AnimController.TargetMovement = targetMovement;
|
||||
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < 0.0f;
|
||||
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
|
||||
}
|
||||
|
||||
if (AnimController is HumanoidAnimController)
|
||||
@@ -1040,7 +1176,8 @@ namespace Barotrauma
|
||||
if (AnimController.onGround &&
|
||||
!AnimController.InWater &&
|
||||
AnimController.Anim != AnimController.Animation.UsingConstruction &&
|
||||
AnimController.Anim != AnimController.Animation.CPR)
|
||||
AnimController.Anim != AnimController.Animation.CPR &&
|
||||
(GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient || Controlled == this))
|
||||
{
|
||||
//Limb head = AnimController.GetLimb(LimbType.Head);
|
||||
// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping.
|
||||
@@ -1058,31 +1195,35 @@ namespace Barotrauma
|
||||
AnimController.TargetDir = Direction.Right;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Server != null && Controlled != this)
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (dequeuedInput.HasFlag(InputNetFlags.FacingLeft))
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
AnimController.TargetDir = Direction.Left;
|
||||
if (dequeuedInput.HasFlag(InputNetFlags.FacingLeft))
|
||||
{
|
||||
AnimController.TargetDir = Direction.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimController.TargetDir = Direction.Right;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (GameMain.NetworkMember.IsClient && Controlled != this)
|
||||
{
|
||||
AnimController.TargetDir = Direction.Right;
|
||||
}
|
||||
}
|
||||
else if (GameMain.Client != null && controlled != this)
|
||||
{
|
||||
if (memState.Count > 0)
|
||||
{
|
||||
AnimController.TargetDir = memState[0].Direction;
|
||||
if (memState.Count > 0)
|
||||
{
|
||||
AnimController.TargetDir = memState[0].Direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove, dev test only
|
||||
#if DEBUG
|
||||
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.F))
|
||||
{
|
||||
AnimController.ReleaseStuckLimbs();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (attackCoolDown > 0.0f)
|
||||
{
|
||||
@@ -1482,129 +1623,6 @@ namespace Barotrauma
|
||||
customInteractHUDText = hudText;
|
||||
}
|
||||
|
||||
private List<Item> debugInteractablesInRange = new List<Item>();
|
||||
private List<Item> debugInteractablesAtCursor = new List<Item>();
|
||||
private List<Pair<Item, float>> debugInteractablesNearCursor = new List<Pair<Item, float>>();
|
||||
|
||||
/// <summary>
|
||||
/// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item.
|
||||
/// </summary>
|
||||
/// <param name="character">The Character who is looking for the interactable item, only items that are close enough to this character are returned</param>
|
||||
/// <param name="simPosition">The item at the simPosition, with the lowest depth, is returned</param>
|
||||
/// <param name="allowFindingNearestItem">If this is true and an item cannot be found at simPosition then a nearest item will be returned if possible</param>
|
||||
/// <param name="hull">If a hull is specified, only items within that hull are returned</param>
|
||||
public Item FindItemAtPosition(Vector2 simPosition, float aimAssistModifier = 0.0f, Hull hull = null, Item[] ignoredItems = null)
|
||||
{
|
||||
if (Submarine != null)
|
||||
{
|
||||
simPosition += Submarine.SimPosition;
|
||||
}
|
||||
|
||||
debugInteractablesInRange.Clear();
|
||||
debugInteractablesAtCursor.Clear();
|
||||
debugInteractablesNearCursor.Clear();
|
||||
|
||||
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
|
||||
|
||||
Item closestItem = null;
|
||||
float closestItemDistance = 0.0f;
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (ignoredItems != null && ignoredItems.Contains(item)) continue;
|
||||
if (hull != null && item.CurrentHull != hull) continue;
|
||||
if (item.body != null && !item.body.Enabled) continue;
|
||||
if (item.ParentInventory != null) continue;
|
||||
|
||||
if (!CanInteractWith(item)) continue;
|
||||
|
||||
debugInteractablesInRange.Add(item);
|
||||
|
||||
float distanceToItem = float.PositiveInfinity;
|
||||
if (item.IsInsideTrigger(displayPosition, out Rectangle transformedTrigger))
|
||||
{
|
||||
debugInteractablesAtCursor.Add(item);
|
||||
//distance is between 0-1 when the cursor is directly on the item
|
||||
distanceToItem =
|
||||
Math.Abs(transformedTrigger.Center.X - displayPosition.X) / transformedTrigger.Width +
|
||||
Math.Abs((transformedTrigger.Y - transformedTrigger.Height / 2.0f) - displayPosition.Y) / transformedTrigger.Height;
|
||||
//modify the distance based on the size of the trigger (preferring smaller items)
|
||||
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (transformedTrigger.Width + transformedTrigger.Height) / 250.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height);
|
||||
|
||||
if (itemDisplayRect.Contains(displayPosition))
|
||||
{
|
||||
debugInteractablesAtCursor.Add(item);
|
||||
//distance is between 0-1 when the cursor is directly on the item
|
||||
distanceToItem =
|
||||
Math.Abs(itemDisplayRect.Center.X - displayPosition.X) / itemDisplayRect.Width +
|
||||
Math.Abs(itemDisplayRect.Center.Y - displayPosition.Y) / itemDisplayRect.Height;
|
||||
//modify the distance based on the size of the item (preferring smaller ones)
|
||||
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (itemDisplayRect.Width + itemDisplayRect.Height) / 250.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
//get the point on the itemDisplayRect which is closest to the cursor
|
||||
Vector2 rectIntersectionPoint = new Vector2(
|
||||
MathHelper.Clamp(displayPosition.X, itemDisplayRect.X, itemDisplayRect.Right),
|
||||
MathHelper.Clamp(displayPosition.Y, itemDisplayRect.Y, itemDisplayRect.Bottom));
|
||||
distanceToItem = 2.0f + Vector2.Distance(rectIntersectionPoint, displayPosition);
|
||||
}
|
||||
}
|
||||
|
||||
//reduce the amount of aim assist if an item has been selected
|
||||
//= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item
|
||||
//otherwise it would be too easy to accidentally switch the selected item when rewiring items
|
||||
float aimAssistAmount = SelectedConstruction == null ? 100.0f * aimAssistModifier : 1.0f;
|
||||
if (distanceToItem < Math.Max(aimAssistAmount, 1.0f))
|
||||
{
|
||||
debugInteractablesNearCursor.Add(new Pair<Item, float>(item, 1.0f - distanceToItem / (100.0f * aimAssistModifier)));
|
||||
if (closestItem == null || distanceToItem < closestItemDistance)
|
||||
{
|
||||
closestItem = item;
|
||||
closestItemDistance = distanceToItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestItem;
|
||||
}
|
||||
|
||||
private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f)
|
||||
{
|
||||
Character closestCharacter = null;
|
||||
float closestDist = 0.0f;
|
||||
|
||||
maxDist = ConvertUnits.ToSimUnits(maxDist);
|
||||
|
||||
foreach (Character c in CharacterList)
|
||||
{
|
||||
if (!CanInteractWith(c)) continue;
|
||||
|
||||
float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition);
|
||||
if (dist < maxDist * maxDist && (closestCharacter == null || dist < closestDist))
|
||||
{
|
||||
closestCharacter = c;
|
||||
closestDist = dist;
|
||||
}
|
||||
|
||||
/*FarseerPhysics.Common.Transform transform;
|
||||
c.AnimController.Collider.FarseerBody.GetTransform(out transform);
|
||||
for (int i = 0; i < c.AnimController.Collider.FarseerBody.FixtureList.Count; i++)
|
||||
{
|
||||
if (c.AnimController.Collider.FarseerBody.FixtureList[i].Shape.TestPoint(ref transform, ref mouseSimPos))
|
||||
{
|
||||
Console.WriteLine("Hit: " + i);
|
||||
closestCharacter = c;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return closestCharacter;
|
||||
}
|
||||
|
||||
private void TransformCursorPos()
|
||||
{
|
||||
if (Submarine == null)
|
||||
@@ -1642,7 +1660,8 @@ namespace Barotrauma
|
||||
|
||||
public void DoInteractionUpdate(float deltaTime, Vector2 mouseSimPos)
|
||||
{
|
||||
bool isLocalPlayer = (controlled == this);
|
||||
bool isLocalPlayer = Controlled == this;
|
||||
|
||||
if (!isLocalPlayer && (this is AICharacter && !IsRemotePlayer))
|
||||
{
|
||||
return;
|
||||
@@ -1690,9 +1709,10 @@ namespace Barotrauma
|
||||
if (SelectedConstruction == null && !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
|
||||
|
||||
bool isControlled = Controlled == this;
|
||||
|
||||
Ladder nearbyLadder = null;
|
||||
if (Controlled == this || climbInput)
|
||||
if (isControlled || climbInput)
|
||||
{
|
||||
float minDist = float.PositiveInfinity;
|
||||
foreach (Ladder ladder in Ladder.List)
|
||||
@@ -1701,7 +1721,7 @@ namespace Barotrauma
|
||||
{
|
||||
minDist = dist;
|
||||
nearbyLadder = ladder;
|
||||
if (Controlled == this) ladder.Item.IsHighlighted = true;
|
||||
if (isControlled) ladder.Item.IsHighlighted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1744,16 +1764,17 @@ namespace Barotrauma
|
||||
}
|
||||
else if (focusedItem != null)
|
||||
{
|
||||
bool canInteract = focusedItem.TryInteract(this);
|
||||
#if CLIENT
|
||||
if (Controlled == this)
|
||||
{
|
||||
focusedItem.IsHighlighted = true;
|
||||
if (canInteract)
|
||||
{
|
||||
CharacterHealth.OpenHealthWindow = null;
|
||||
}
|
||||
}
|
||||
if (focusedItem.TryInteract(this))
|
||||
{
|
||||
#if CLIENT
|
||||
if (Controlled == this) CharacterHealth.OpenHealthWindow = null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (IsKeyHit(InputType.Select) && SelectedConstruction != null)
|
||||
{
|
||||
@@ -1776,16 +1797,16 @@ namespace Barotrauma
|
||||
|
||||
public static void UpdateAll(float deltaTime, Camera cam)
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
foreach (Character c in CharacterList)
|
||||
{
|
||||
if (!(c is AICharacter) && !c.IsRemotePlayer) continue;
|
||||
|
||||
if (GameMain.Server != null)
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
|
||||
if (c == controlled || c.IsRemotePlayer)
|
||||
if (c == Controlled || c.IsRemotePlayer)
|
||||
{
|
||||
c.Enabled = true;
|
||||
}
|
||||
@@ -1794,8 +1815,7 @@ namespace Barotrauma
|
||||
float distSqr = float.MaxValue;
|
||||
foreach (Character otherCharacter in CharacterList)
|
||||
{
|
||||
if (otherCharacter == c) { continue; }
|
||||
if (!otherCharacter.IsRemotePlayer && otherCharacter != GameMain.NetworkMember.Character) { continue; }
|
||||
if (otherCharacter == c || !otherCharacter.IsRemotePlayer) { continue; }
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(otherCharacter.WorldPosition, c.WorldPosition));
|
||||
}
|
||||
|
||||
@@ -1815,7 +1835,7 @@ namespace Barotrauma
|
||||
float distSqr = Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition);
|
||||
if (Controlled != null)
|
||||
{
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(controlled.WorldPosition, c.WorldPosition));
|
||||
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(Controlled.WorldPosition, c.WorldPosition));
|
||||
}
|
||||
|
||||
if (distSqr > NetConfig.DisableCharacterDistSqr)
|
||||
@@ -1827,6 +1847,7 @@ namespace Barotrauma
|
||||
c.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1839,8 +1860,8 @@ namespace Barotrauma
|
||||
public virtual void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateProjSpecific(deltaTime, cam);
|
||||
|
||||
if (GameMain.Client != null && this == Controlled && !isSynced) return;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && this == Controlled && !isSynced) return;
|
||||
|
||||
if (!Enabled) return;
|
||||
|
||||
@@ -1910,8 +1931,8 @@ namespace Barotrauma
|
||||
|
||||
if (PressureTimer >= 100.0f)
|
||||
{
|
||||
if (controlled == this) cam.Zoom = 5.0f;
|
||||
if (GameMain.Client == null)
|
||||
if (Controlled == this) cam.Zoom = 5.0f;
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
Implode();
|
||||
return;
|
||||
@@ -1923,7 +1944,7 @@ namespace Barotrauma
|
||||
PressureTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
else if (GameMain.Client == null && WorldPosition.Y < CharacterHealth.CrushDepth)
|
||||
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && WorldPosition.Y < CharacterHealth.CrushDepth)
|
||||
{
|
||||
//implode if below crush depth, and either outside or in a high-pressure hull
|
||||
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
|
||||
@@ -1950,9 +1971,10 @@ namespace Barotrauma
|
||||
UpdateAIChatMessages(deltaTime);
|
||||
|
||||
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
|
||||
bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true;
|
||||
if (IsForceRagdolled)
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
else if ((GameMain.Server == null || GameMain.Server.AllowRagdollButton) && (!IsRagdolled || AnimController.Collider.LinearVelocity.Length() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll
|
||||
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
|
||||
UpdateSightRange();
|
||||
@@ -1974,7 +1996,10 @@ namespace Barotrauma
|
||||
//AI and control stuff
|
||||
|
||||
Control(deltaTime, cam);
|
||||
if (controlled != this && (!(this is AICharacter) || IsRemotePlayer))
|
||||
|
||||
bool isNotControlled = Controlled != this;
|
||||
|
||||
if (isNotControlled && (!(this is AICharacter) || IsRemotePlayer))
|
||||
{
|
||||
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
|
||||
DoInteractionUpdate(deltaTime, mouseSimPos);
|
||||
@@ -2065,7 +2090,7 @@ namespace Barotrauma
|
||||
|
||||
public void Speak(string message, ChatMessageType? messageType, float delay = 0.0f, string identifier = "", float minDurationBetweenSimilar = 0.0f)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
|
||||
//already sent a similar message a moment ago
|
||||
if (!string.IsNullOrEmpty(identifier) && minDurationBetweenSimilar > 0.0f &&
|
||||
@@ -2080,7 +2105,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateAIChatMessages(float deltaTime)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
|
||||
List<AIChatMessage> sentMessages = new List<AIChatMessage>();
|
||||
foreach (AIChatMessage message in aiChatMessageQueue)
|
||||
@@ -2102,10 +2127,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && message.MessageType != ChatMessageType.Order)
|
||||
{
|
||||
GameMain.Server.SendChatMessage(message.Message, message.MessageType.Value, null, this);
|
||||
}
|
||||
#endif
|
||||
ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.MessageType.Value]);
|
||||
sentMessages.Add(message);
|
||||
}
|
||||
@@ -2134,23 +2161,7 @@ namespace Barotrauma
|
||||
speechBubbleColor = color;
|
||||
}
|
||||
|
||||
private void AdjustKarma(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
if (GameMain.Server == null || attacker == null) return;
|
||||
|
||||
Client attackerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker);
|
||||
if (attackerClient == null) return;
|
||||
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||
if (targetClient != null || this == Controlled)
|
||||
{
|
||||
if (attacker.TeamID == TeamID)
|
||||
{
|
||||
attackerClient.Karma -= attackResult.Damage * 0.01f;
|
||||
if (CharacterHealth.MaxVitality <= CharacterHealth.MinVitality) attackerClient.Karma = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
partial void AdjustKarma(Character attacker, AttackResult attackResult);
|
||||
|
||||
partial void DamageHUD(float amount);
|
||||
|
||||
@@ -2186,9 +2197,9 @@ namespace Barotrauma
|
||||
DamageLimb(worldPosition, targetLimb, attack.Afflictions, attack.Stun, playSound, attackImpulse, attacker);
|
||||
|
||||
if (limbHit == null) return new AttackResult();
|
||||
|
||||
|
||||
limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime);
|
||||
|
||||
#if SERVER
|
||||
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)
|
||||
{
|
||||
string logMsg = LogName + " attacked by " + attackingCharacter.LogName + ".";
|
||||
@@ -2202,8 +2213,11 @@ namespace Barotrauma
|
||||
}
|
||||
GameServer.Log(logMsg, ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.Client == null &&
|
||||
bool isNotClient = GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient;
|
||||
|
||||
if (isNotClient &&
|
||||
IsDead && Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability)
|
||||
{
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
@@ -2264,17 +2278,21 @@ namespace Barotrauma
|
||||
if (Removed) return new AttackResult();
|
||||
|
||||
SetStun(stun);
|
||||
|
||||
Vector2 dir = hitLimb.WorldPosition - worldPosition;
|
||||
if (Math.Abs(attackImpulse) > 0.0f)
|
||||
{
|
||||
Vector2 diff = hitLimb.WorldPosition - worldPosition;
|
||||
Vector2 diff = dir;
|
||||
if (diff == Vector2.Zero) diff = Rand.Vector(1.0f);
|
||||
hitLimb.body.ApplyLinearImpulse(Vector2.Normalize(diff) * attackImpulse, hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff));
|
||||
}
|
||||
|
||||
AttackResult attackResult = hitLimb.AddDamage(worldPosition, afflictions, playSound);
|
||||
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
|
||||
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound);
|
||||
CharacterHealth.ApplyDamage(hitLimb, attackResult);
|
||||
if (attacker != this) { OnAttacked?.Invoke(attacker, attackResult); };
|
||||
if (attacker != this)
|
||||
{
|
||||
OnAttacked?.Invoke(attacker, attackResult);
|
||||
OnAttackedProjSpecific(attacker, attackResult);
|
||||
};
|
||||
AdjustKarma(attacker, attackResult);
|
||||
|
||||
if (attacker != null && attackResult.Damage > 0.0f)
|
||||
@@ -2285,9 +2303,11 @@ namespace Barotrauma
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult);
|
||||
|
||||
public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false)
|
||||
{
|
||||
if (GameMain.Client != null && !isNetworkMessage) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) return;
|
||||
|
||||
if ((newStun <= Stun && !allowStunDecrease) || !MathUtils.IsValid(newStun)) return;
|
||||
|
||||
@@ -2304,8 +2324,18 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (StatusEffect statusEffect in statusEffects)
|
||||
{
|
||||
if (statusEffect.type != actionType) continue;
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
if (statusEffect.type != actionType) { continue; }
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
statusEffect.GetNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, this, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2315,7 +2345,7 @@ namespace Barotrauma
|
||||
|
||||
if (!isNetworkMessage)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
}
|
||||
|
||||
Kill(CauseOfDeathType.Pressure, null, isNetworkMessage);
|
||||
@@ -2359,8 +2389,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (IsDead || CharacterHealth.Unkillable) { return; }
|
||||
|
||||
HealthUpdateInterval = 0.0f;
|
||||
|
||||
//clients aren't allowed to kill characters unless they receive a network message
|
||||
if (!isNetworkMessage && GameMain.Client != null)
|
||||
if (!isNetworkMessage && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -2369,19 +2401,11 @@ namespace Barotrauma
|
||||
|
||||
AnimController.Frozen = false;
|
||||
|
||||
if (causeOfDeath == CauseOfDeathType.Affliction)
|
||||
{
|
||||
GameServer.Log(LogName + " has died (Cause of death: " + causeOfDeathAffliction.Prefab.Name + ")", ServerLog.MessageType.Attack);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log(LogName + " has died (Cause of death: " + causeOfDeath + ")", ServerLog.MessageType.Attack);
|
||||
}
|
||||
|
||||
if (GameSettings.SendUserStatistics)
|
||||
{
|
||||
string characterType = "Unknown";
|
||||
if (this == controlled)
|
||||
|
||||
if (this == Controlled)
|
||||
characterType = "Player";
|
||||
else if (IsRemotePlayer)
|
||||
characterType = "RemotePlayer";
|
||||
@@ -2402,7 +2426,7 @@ namespace Barotrauma
|
||||
|
||||
SteamAchievementManager.OnCharacterKilled(this, CauseOfDeath);
|
||||
|
||||
KillProjSpecific();
|
||||
KillProjSpecific(causeOfDeath, causeOfDeathAffliction);
|
||||
|
||||
IsDead = true;
|
||||
|
||||
@@ -2427,7 +2451,7 @@ namespace Barotrauma
|
||||
GameMain.GameSession.KillCharacter(this);
|
||||
}
|
||||
}
|
||||
partial void KillProjSpecific();
|
||||
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction);
|
||||
|
||||
public void Revive()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -49,12 +48,12 @@ namespace Barotrauma
|
||||
public XElement BeardElement { get; set; }
|
||||
public XElement MoustacheElement { get; set; }
|
||||
public XElement FaceAttachment { get; set; }
|
||||
|
||||
|
||||
public HeadInfo() { }
|
||||
|
||||
public HeadInfo(int headId)
|
||||
{
|
||||
_headSpriteId = headId;
|
||||
_headSpriteId = Math.Max(headId, 1);
|
||||
}
|
||||
|
||||
public void ResetAttachmentIndices()
|
||||
@@ -66,7 +65,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private HeadInfo head = new HeadInfo();
|
||||
private HeadInfo head;
|
||||
public HeadInfo Head
|
||||
{
|
||||
get { return head; }
|
||||
@@ -79,10 +78,6 @@ namespace Barotrauma
|
||||
{
|
||||
head.race = GetRandomRace();
|
||||
}
|
||||
if (head.gender == Gender.None)
|
||||
{
|
||||
head.gender = GetRandomGender();
|
||||
}
|
||||
CalculateHeadSpriteRange();
|
||||
Head.HeadSpriteId = value.HeadSpriteId;
|
||||
HeadSprite = null;
|
||||
@@ -101,16 +96,15 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
string disguiseName = "?";
|
||||
if (Character == null || !Character.HideFace || (GameMain.Server != null && !GameMain.Server.AllowDisguises))
|
||||
if (Character == null || !Character.HideFace)
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !GameMain.Client.AllowDisguises)
|
||||
else if ((GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowDisguises))
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Character.Inventory != null)
|
||||
{
|
||||
int cardSlotIndex = Character.Inventory.FindLimbSlot(InvSlotType.Card);
|
||||
@@ -134,6 +128,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public string SpeciesName => SourceElement.GetAttributeString("name", string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Note: Can be null.
|
||||
/// </summary>
|
||||
@@ -237,78 +233,11 @@ namespace Barotrauma
|
||||
|
||||
public readonly string ragdollFileName = string.Empty;
|
||||
|
||||
private Sprite headSprite;
|
||||
public Sprite HeadSprite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (headSprite == null)
|
||||
{
|
||||
LoadHeadSprite();
|
||||
}
|
||||
return headSprite;
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite portrait;
|
||||
public Sprite Portrait
|
||||
{
|
||||
get
|
||||
{
|
||||
if (portrait == null)
|
||||
{
|
||||
LoadHeadSprite();
|
||||
}
|
||||
return portrait;
|
||||
}
|
||||
}
|
||||
|
||||
private Sprite clothingSprite;
|
||||
public Sprite ClothingSprite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (clothingSprite == null)
|
||||
{
|
||||
if (Job != null && Job.Prefab.ClothingElement != null)
|
||||
{
|
||||
clothingSprite = new Sprite(Job.Prefab.ClothingElement.Element("sprite"));
|
||||
}
|
||||
}
|
||||
return clothingSprite;
|
||||
}
|
||||
}
|
||||
|
||||
private NPCPersonalityTrait personalityTrait;
|
||||
|
||||
//unique ID given to character infos in MP
|
||||
//used by clients to identify which infos are the same to prevent duplicate characters in round summary
|
||||
public ushort ID;
|
||||
|
||||
public XElement InventoryData;
|
||||
|
||||
|
||||
public XElement SourceElement { get; set; }
|
||||
|
||||
public XElement HairElement { get; private set; }
|
||||
public XElement BeardElement { get; private set; }
|
||||
public XElement MoustacheElement { get; private set; }
|
||||
public XElement FaceAttachment { get; private set; }
|
||||
|
||||
public int HairIndex { get; set; } = -1;
|
||||
public int BeardIndex { get; set; } = -1;
|
||||
public int MoustacheIndex { get; set; } = -1;
|
||||
public int FaceAttachmentIndex { get; set; } = -1;
|
||||
|
||||
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
|
||||
|
||||
public readonly string ragdollFileName = string.Empty;
|
||||
|
||||
public bool StartItemsGiven;
|
||||
|
||||
public CauseOfDeath CauseOfDeath;
|
||||
|
||||
public byte TeamID;
|
||||
public Character.TeamType TeamID;
|
||||
|
||||
private NPCPersonalityTrait personalityTrait;
|
||||
|
||||
@@ -317,7 +246,6 @@ namespace Barotrauma
|
||||
public ushort ID;
|
||||
|
||||
public XElement InventoryData;
|
||||
|
||||
|
||||
public List<string> SpriteTags
|
||||
{
|
||||
@@ -345,7 +273,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Gender gender;
|
||||
public readonly bool HasGenders;
|
||||
|
||||
public Gender Gender
|
||||
{
|
||||
get { return Head.gender; }
|
||||
@@ -399,7 +328,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (ragdoll == null)
|
||||
{
|
||||
string speciesName = SourceElement.GetAttributeString("name", string.Empty);
|
||||
string speciesName = SpeciesName;
|
||||
bool isHumanoid = SourceElement.GetAttributeBool("humanoid", false);
|
||||
ragdoll = isHumanoid
|
||||
? HumanRagdollParams.GetRagdollParams(speciesName, ragdollFileName)
|
||||
@@ -413,7 +342,7 @@ namespace Barotrauma
|
||||
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
|
||||
|
||||
// Used for creating the data
|
||||
public CharacterInfo(string file, string name = "", Gender gender = Gender.None, JobPrefab jobPrefab = null, string ragdollFileName = null)
|
||||
public CharacterInfo(string file, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null)
|
||||
{
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
@@ -421,18 +350,13 @@ namespace Barotrauma
|
||||
SpriteTags = new List<string>();
|
||||
XDocument doc = GetConfig(file);
|
||||
SourceElement = doc.Root;
|
||||
if (doc.Root.GetAttributeBool("genders", false))
|
||||
head = new HeadInfo();
|
||||
HasGenders = doc.Root.GetAttributeBool("genders", false);
|
||||
if (HasGenders)
|
||||
{
|
||||
Head.gender = gender == Gender.None ? GetRandomGender() : gender;
|
||||
}
|
||||
if (!Enum.TryParse(doc.Root.GetAttributeString("race", "None"), true, out Head.race))
|
||||
{
|
||||
Head.race = GetRandomRace();
|
||||
}
|
||||
if (Head.race == Race.None)
|
||||
{
|
||||
Head.race = GetRandomRace();
|
||||
Head.gender = GetRandomGender();
|
||||
}
|
||||
Head.race = GetRandomRace();
|
||||
CalculateHeadSpriteRange();
|
||||
Head.HeadSpriteId = GetRandomHeadID();
|
||||
Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Server) : new Job(jobPrefab);
|
||||
@@ -477,16 +401,28 @@ namespace Barotrauma
|
||||
idCounter++;
|
||||
Name = element.GetAttributeString("name", "unnamed");
|
||||
string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
Head.gender = (genderStr == "male") ? Gender.Male : Gender.Female;
|
||||
Enum.TryParse(element.GetAttributeString("race", "white"), true, out Head.race);
|
||||
File = element.GetAttributeString("file", "");
|
||||
SourceElement = GetConfig(File).Root;
|
||||
HasGenders = SourceElement.GetAttributeBool("genders", false);
|
||||
Salary = element.GetAttributeInt("salary", 1000);
|
||||
Head.HeadSpriteId = element.GetAttributeInt("headspriteid", 1);
|
||||
Head.HairIndex = element.GetAttributeInt("hairindex", -1);
|
||||
Head.BeardIndex = element.GetAttributeInt("beardindex", -1);
|
||||
Head.MoustacheIndex = element.GetAttributeInt("moustacheindex", -1);
|
||||
Head.FaceAttachmentIndex = element.GetAttributeInt("faceattachmentindex", -1);
|
||||
Enum.TryParse(element.GetAttributeString("race", "White"), true, out Race race);
|
||||
Enum.TryParse(element.GetAttributeString("gender", "None"), true, out Gender gender);
|
||||
if (HasGenders && gender == Gender.None)
|
||||
{
|
||||
gender = GetRandomGender();
|
||||
}
|
||||
else if (!HasGenders)
|
||||
{
|
||||
gender = Gender.None;
|
||||
}
|
||||
RecreateHead(
|
||||
element.GetAttributeInt("headspriteid", 1),
|
||||
race,
|
||||
gender,
|
||||
element.GetAttributeInt("hairindex", -1),
|
||||
element.GetAttributeInt("beardindex", -1),
|
||||
element.GetAttributeInt("moustacheindex", -1),
|
||||
element.GetAttributeInt("faceattachmentindex", -1));
|
||||
StartItemsGiven = element.GetAttributeBool("startitemsgiven", false);
|
||||
string personalityName = element.GetAttributeString("personality", "");
|
||||
ragdollFileName = element.GetAttributeString("ragdoll", string.Empty);
|
||||
@@ -503,50 +439,6 @@ namespace Barotrauma
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
|
||||
public void LoadHeadSprite()
|
||||
{
|
||||
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
|
||||
{
|
||||
if (limbElement.GetAttributeString("type", "").ToLowerInvariant() != "head") continue;
|
||||
|
||||
XElement spriteElement = limbElement.Element("sprite");
|
||||
|
||||
string spritePath = spriteElement.Attribute("texture").Value;
|
||||
|
||||
spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant());
|
||||
spritePath = spritePath.Replace("[HEADID]", HeadSpriteId.ToString());
|
||||
|
||||
string fileName = Path.GetFileNameWithoutExtension(spritePath);
|
||||
|
||||
//go through the files in the directory to find a matching sprite
|
||||
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
|
||||
{
|
||||
if (!file.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
|
||||
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
|
||||
if (fileWithoutTags != fileName) continue;
|
||||
|
||||
HeadSprite = new Sprite(spriteElement, "", file);
|
||||
Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
|
||||
|
||||
//extract the tags out of the filename
|
||||
SpriteTags = file.Split('[', ']').Skip(1).ToList();
|
||||
if (SpriteTags.Any())
|
||||
{
|
||||
SpriteTags.RemoveAt(SpriteTags.Count-1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private XDocument GetConfig(string file)
|
||||
{
|
||||
if (!cachedConfigs.TryGetValue(file, out XDocument doc))
|
||||
@@ -558,8 +450,6 @@ namespace Barotrauma
|
||||
return doc;
|
||||
}
|
||||
|
||||
public Gender SetRandomGender() => Gender = GetRandomGender();
|
||||
public Race SetRandomRace() => Race = GetRandomRace();
|
||||
public int SetRandomHead() => HeadSpriteId = GetRandomHeadID();
|
||||
|
||||
public Gender GetRandomGender() => (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < SourceElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male;
|
||||
@@ -592,7 +482,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (elements == null) { return elements; }
|
||||
return elements.Where(w =>
|
||||
Enum.TryParse(w.GetAttributeString("gender", "male"), true, out Gender g) && g == Head.gender &&
|
||||
Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == Head.gender &&
|
||||
Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == Head.race);
|
||||
}
|
||||
|
||||
@@ -600,11 +490,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (SourceElement == null) { return; }
|
||||
Head.headSpriteRange = SourceElement.GetAttributeVector2("headidrange", Vector2.Zero);
|
||||
// If range is defined, we use it as it is
|
||||
// Else we calculate the range from the wearables.
|
||||
if (Head.headSpriteRange == Vector2.Zero)
|
||||
{
|
||||
// If range is defined, we use it as it is
|
||||
// Else we calculate the range from the wearables.
|
||||
var wearables = FilterElementsByGenderAndRace(Wearables);
|
||||
var wearableElements = Wearables;
|
||||
if (wearableElements == null) { return; }
|
||||
var wearables = FilterElementsByGenderAndRace(wearableElements).ToList();
|
||||
if (wearables == null)
|
||||
{
|
||||
Head.headSpriteRange = Vector2.Zero;
|
||||
@@ -630,6 +522,74 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void RecreateHead(int headID, Race race, Gender gender, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex)
|
||||
{
|
||||
if (HasGenders && gender == Gender.None)
|
||||
{
|
||||
gender = GetRandomGender();
|
||||
}
|
||||
else if (!HasGenders)
|
||||
{
|
||||
gender = Gender.None;
|
||||
}
|
||||
|
||||
head = new HeadInfo(headID)
|
||||
{
|
||||
race = race,
|
||||
gender = gender,
|
||||
HairIndex = hairIndex,
|
||||
BeardIndex = beardIndex,
|
||||
MoustacheIndex = moustacheIndex,
|
||||
FaceAttachmentIndex = faceAttachmentIndex
|
||||
};
|
||||
CalculateHeadSpriteRange();
|
||||
ReloadHeadAttachments();
|
||||
}
|
||||
|
||||
public void LoadHeadSprite()
|
||||
{
|
||||
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
|
||||
{
|
||||
if (limbElement.GetAttributeString("type", "").ToLowerInvariant() != "head") continue;
|
||||
|
||||
XElement spriteElement = limbElement.Element("sprite");
|
||||
|
||||
string spritePath = spriteElement.Attribute("texture").Value;
|
||||
|
||||
spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
|
||||
spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant());
|
||||
spritePath = spritePath.Replace("[HEADID]", HeadSpriteId.ToString());
|
||||
|
||||
string fileName = Path.GetFileNameWithoutExtension(spritePath);
|
||||
|
||||
//go through the files in the directory to find a matching sprite
|
||||
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
|
||||
{
|
||||
if (!file.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
|
||||
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
|
||||
if (fileWithoutTags != fileName) continue;
|
||||
|
||||
HeadSprite = new Sprite(spriteElement, "", file);
|
||||
Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
|
||||
|
||||
//extract the tags out of the filename
|
||||
SpriteTags = file.Split('[', ']').Skip(1).ToList();
|
||||
if (SpriteTags.Any())
|
||||
{
|
||||
SpriteTags.RemoveAt(SpriteTags.Count - 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads only the elements according to the indices, not the sprites.
|
||||
/// </summary>
|
||||
@@ -762,7 +722,7 @@ namespace Barotrauma
|
||||
|
||||
public void IncreaseSkillLevel(string skillIdentifier, float increase, Vector2 worldPos)
|
||||
{
|
||||
if (Job == null || GameMain.Client != null) return;
|
||||
if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)) return;
|
||||
|
||||
float prevLevel = Job.GetSkillLevel(skillIdentifier);
|
||||
Job.IncreaseSkillLevel(skillIdentifier, increase);
|
||||
@@ -771,9 +731,9 @@ namespace Barotrauma
|
||||
|
||||
OnSkillChanged(skillIdentifier, prevLevel, newLevel, worldPos);
|
||||
|
||||
if (GameMain.Server != null && (int)newLevel != (int)prevLevel)
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && (int)newLevel != (int)prevLevel)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateSkills });
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateSkills });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,7 +805,7 @@ namespace Barotrauma
|
||||
int slotIndex = itemElement.GetAttributeInt("i", 0);
|
||||
if (newItem == null) continue;
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(newItem, false);
|
||||
SpawnInventoryItemProjSpecific(newItem);
|
||||
|
||||
inventory.TryPutItem(newItem, slotIndex, false, false, null);
|
||||
|
||||
@@ -860,96 +820,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg)
|
||||
{
|
||||
msg.Write(ID);
|
||||
msg.Write(Name);
|
||||
msg.Write(Gender == Gender.Female);
|
||||
msg.Write((byte)Race);
|
||||
msg.Write((byte)HeadSpriteId);
|
||||
msg.Write((byte)Head.HairIndex);
|
||||
msg.Write((byte)Head.BeardIndex);
|
||||
msg.Write((byte)Head.MoustacheIndex);
|
||||
msg.Write((byte)Head.FaceAttachmentIndex);
|
||||
msg.Write(ragdollFileName);
|
||||
|
||||
if (Job != null)
|
||||
{
|
||||
msg.Write(Job.Prefab.Identifier);
|
||||
msg.Write((byte)Job.Skills.Count);
|
||||
foreach (Skill skill in Job.Skills)
|
||||
{
|
||||
msg.Write(skill.Identifier);
|
||||
msg.Write(skill.Level);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write("");
|
||||
}
|
||||
// TODO: animations
|
||||
}
|
||||
|
||||
public static CharacterInfo ClientRead(string configPath, NetBuffer inc)
|
||||
{
|
||||
ushort infoID = inc.ReadUInt16();
|
||||
string newName = inc.ReadString();
|
||||
bool isFemale = inc.ReadBoolean();
|
||||
int race = inc.ReadByte();
|
||||
int headSpriteID = inc.ReadByte();
|
||||
int hairIndex = inc.ReadByte();
|
||||
int beardIndex = inc.ReadByte();
|
||||
int moustacheIndex = inc.ReadByte();
|
||||
int faceAttachmentIndex = inc.ReadByte();
|
||||
string ragdollFile = inc.ReadString();
|
||||
|
||||
string jobIdentifier = inc.ReadString();
|
||||
JobPrefab jobPrefab = null;
|
||||
Dictionary<string, float> skillLevels = new Dictionary<string, float>();
|
||||
if (!string.IsNullOrEmpty(jobIdentifier))
|
||||
{
|
||||
jobPrefab = JobPrefab.List.Find(jp => jp.Identifier == jobIdentifier);
|
||||
int skillCount = inc.ReadByte();
|
||||
for (int i = 0; i < skillCount; i++)
|
||||
{
|
||||
string skillIdentifier = inc.ReadString();
|
||||
float skillLevel = inc.ReadSingle();
|
||||
skillLevels.Add(skillIdentifier, skillLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: animations
|
||||
|
||||
CharacterInfo ch = new CharacterInfo(configPath, newName, isFemale ? Gender.Female : Gender.Male, jobPrefab, ragdollFile)
|
||||
{
|
||||
ID = infoID,
|
||||
};
|
||||
ch.Head.race = (Race)race;
|
||||
ch.Head.HeadSpriteId = headSpriteID;
|
||||
ch.HairIndex = hairIndex;
|
||||
ch.BeardIndex = beardIndex;
|
||||
ch.MoustacheIndex = moustacheIndex;
|
||||
ch.FaceAttachmentIndex = faceAttachmentIndex;
|
||||
ch.CalculateHeadSpriteRange();
|
||||
ch.ReloadHeadAttachments();
|
||||
|
||||
System.Diagnostics.Debug.Assert(skillLevels.Count == ch.Job.Skills.Count);
|
||||
if (ch.Job != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, float> skill in skillLevels)
|
||||
{
|
||||
Skill matchingSkill = ch.Job.Skills.Find(s => s.Identifier == skill.Key);
|
||||
if (matchingSkill == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Skill \"" + skill.Key + "\" not found in character \"" + newName + "\"");
|
||||
continue;
|
||||
}
|
||||
matchingSkill.Level = skill.Value;
|
||||
}
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
partial void SpawnInventoryItemProjSpecific(Item item);
|
||||
|
||||
public void ReloadHeadAttachments()
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -15,18 +13,18 @@ namespace Barotrauma
|
||||
|
||||
public readonly AnimController.Animation Animation;
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float rotation, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, 0, time, dir, interact, animation)
|
||||
public CharacterStateInfo(Vector2 pos, float rotation, Vector2 velocity, float angularVelocity, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, interact, animation)
|
||||
{
|
||||
}
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float rotation, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, rotation, ID, 0.0f, dir, interact, animation)
|
||||
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, interact, animation)
|
||||
{
|
||||
}
|
||||
|
||||
protected CharacterStateInfo(Vector2 pos, float rotation, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: base(pos, rotation, ID, time)
|
||||
protected CharacterStateInfo(Vector2 pos, float rotation, Vector2 velocity, float angularVelocity, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: base(pos, rotation, velocity, angularVelocity, ID, time)
|
||||
{
|
||||
Direction = dir;
|
||||
Interact = interact;
|
||||
@@ -81,16 +79,22 @@ namespace Barotrauma
|
||||
|
||||
private List<CharacterStateInfo> memState = new List<CharacterStateInfo>();
|
||||
private List<CharacterStateInfo> memLocalState = new List<CharacterStateInfo>();
|
||||
|
||||
public float healthUpdateTimer;
|
||||
|
||||
private float healthUpdateInterval;
|
||||
public float HealthUpdateInterval
|
||||
{
|
||||
get { return healthUpdateInterval; }
|
||||
set
|
||||
{
|
||||
healthUpdateInterval = MathHelper.Clamp(value, 0.0f, IsDead ? NetConfig.MaxHealthUpdateIntervalDead : NetConfig.MaxHealthUpdateInterval);
|
||||
healthUpdateTimer = Math.Min(healthUpdateTimer, healthUpdateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private bool networkUpdateSent;
|
||||
|
||||
public bool isSynced = false;
|
||||
|
||||
public string OwnerClientIP;
|
||||
public string OwnerClientName;
|
||||
public bool ClientDisconnected;
|
||||
public float KillDisconnectedTimer;
|
||||
|
||||
|
||||
public List<CharacterStateInfo> MemState
|
||||
{
|
||||
get { return memState; }
|
||||
@@ -110,512 +114,7 @@ namespace Barotrauma
|
||||
LastNetworkUpdateID = 0;
|
||||
LastProcessedID = 0;
|
||||
}
|
||||
|
||||
private void UpdateNetInput()
|
||||
{
|
||||
if (this != Controlled)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
#if CLIENT
|
||||
//freeze AI characters if more than 1 seconds have passed since last update from the server
|
||||
if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f)
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
memState.Clear();
|
||||
//hide after 2 seconds
|
||||
if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f)
|
||||
{
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (GameMain.Server != null && (!(this is AICharacter) || IsRemotePlayer))
|
||||
{
|
||||
if (!AllowInput)
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
if (memInput.Count > 0)
|
||||
{
|
||||
prevDequeuedInput = dequeuedInput;
|
||||
dequeuedInput = memInput[memInput.Count - 1].states;
|
||||
memInput.RemoveAt(memInput.Count - 1);
|
||||
}
|
||||
}
|
||||
else if (memInput.Count == 0)
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
prevDequeuedInput = dequeuedInput;
|
||||
|
||||
LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
|
||||
dequeuedInput = memInput[memInput.Count - 1].states;
|
||||
|
||||
double aimAngle = ((double)memInput[memInput.Count - 1].intAim / 65535.0) * 2.0 * Math.PI;
|
||||
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;
|
||||
|
||||
//reset focus when attempting to use/select something
|
||||
if (memInput[memInput.Count - 1].states.HasFlag(InputNetFlags.Use) ||
|
||||
memInput[memInput.Count - 1].states.HasFlag(InputNetFlags.Select) ||
|
||||
memInput[memInput.Count - 1].states.HasFlag(InputNetFlags.Health) ||
|
||||
memInput[memInput.Count - 1].states.HasFlag(InputNetFlags.Grab))
|
||||
{
|
||||
focusedItem = null;
|
||||
focusedCharacter = null;
|
||||
}
|
||||
var closestEntity = FindEntityByID(memInput[memInput.Count - 1].interact);
|
||||
if (closestEntity is Item)
|
||||
{
|
||||
if (CanInteractWith((Item)closestEntity))
|
||||
{
|
||||
focusedItem = (Item)closestEntity;
|
||||
focusedCharacter = null;
|
||||
}
|
||||
}
|
||||
else if (closestEntity is Character)
|
||||
{
|
||||
if (CanInteractWith((Character)closestEntity))
|
||||
{
|
||||
focusedCharacter = (Character)closestEntity;
|
||||
focusedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
memInput.RemoveAt(memInput.Count - 1);
|
||||
|
||||
TransformCursorPos();
|
||||
|
||||
if ((dequeuedInput == InputNetFlags.None || dequeuedInput == InputNetFlags.FacingLeft) && Math.Abs(AnimController.Collider.LinearVelocity.X) < 0.005f && Math.Abs(AnimController.Collider.LinearVelocity.Y) < 0.2f)
|
||||
{
|
||||
while (memInput.Count > 5 && memInput[memInput.Count - 1].states == dequeuedInput)
|
||||
{
|
||||
//remove inputs where the player is not moving at all
|
||||
//helps the server catch up, shouldn't affect final position
|
||||
LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
|
||||
memInput.RemoveAt(memInput.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(
|
||||
SimPosition,
|
||||
AnimController.Collider.Rotation,
|
||||
LastNetworkUpdateID,
|
||||
AnimController.TargetDir,
|
||||
SelectedCharacter == null ? (Entity)SelectedConstruction : (Entity)SelectedCharacter,
|
||||
AnimController.Anim);
|
||||
|
||||
memLocalState.Add(posInfo);
|
||||
|
||||
InputNetFlags newInput = InputNetFlags.None;
|
||||
if (IsKeyDown(InputType.Left)) newInput |= InputNetFlags.Left;
|
||||
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
|
||||
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
|
||||
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
|
||||
if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
|
||||
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
|
||||
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
|
||||
if (IsKeyHit(InputType.Health)) newInput |= InputNetFlags.Health;
|
||||
if (IsKeyHit(InputType.Grab)) newInput |= InputNetFlags.Grab;
|
||||
if (IsKeyDown(InputType.Use)) newInput |= InputNetFlags.Use;
|
||||
if (IsKeyDown(InputType.Aim)) newInput |= InputNetFlags.Aim;
|
||||
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
|
||||
if (IsKeyDown(InputType.Ragdoll)) newInput |= InputNetFlags.Ragdoll;
|
||||
|
||||
if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
|
||||
|
||||
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
|
||||
relativeCursorPos.Normalize();
|
||||
UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));
|
||||
|
||||
NetInputMem newMem = new NetInputMem
|
||||
{
|
||||
states = newInput,
|
||||
intAim = intAngle
|
||||
};
|
||||
if (focusedItem != null && (!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health)))
|
||||
{
|
||||
newMem.interact = focusedItem.ID;
|
||||
}
|
||||
else if (focusedCharacter != null)
|
||||
{
|
||||
newMem.interact = focusedCharacter.ID;
|
||||
}
|
||||
|
||||
memInput.Insert(0, newMem);
|
||||
LastNetworkUpdateID++;
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
memInput.RemoveRange(60, memInput.Count - 60);
|
||||
}
|
||||
}
|
||||
else //this == Character.Controlled && GameMain.Client == null
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
}
|
||||
|
||||
if (networkUpdateSent)
|
||||
{
|
||||
foreach (Key key in keys)
|
||||
{
|
||||
key.DequeueHit();
|
||||
key.DequeueHeld();
|
||||
}
|
||||
|
||||
networkUpdateSent = false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ClientNetObject.CHARACTER_INPUT:
|
||||
|
||||
if (c.Character != this)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
UInt16 networkUpdateID = msg.ReadUInt16();
|
||||
byte inputCount = msg.ReadByte();
|
||||
|
||||
if (AllowInput) Enabled = true;
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
InputNetFlags newInput = (InputNetFlags)msg.ReadRangedInteger(0, (int)InputNetFlags.MaxVal);
|
||||
UInt16 newAim = 0;
|
||||
UInt16 newInteract = 0;
|
||||
|
||||
if (newInput != InputNetFlags.None && newInput != InputNetFlags.FacingLeft)
|
||||
{
|
||||
c.KickAFKTimer = 0.0f;
|
||||
}
|
||||
else if (AnimController.Dir < 0.0f != newInput.HasFlag(InputNetFlags.FacingLeft))
|
||||
{
|
||||
//character changed the direction they're facing
|
||||
c.KickAFKTimer = 0.0f;
|
||||
}
|
||||
|
||||
if (newInput.HasFlag(InputNetFlags.Aim))
|
||||
{
|
||||
newAim = msg.ReadUInt16();
|
||||
}
|
||||
if (newInput.HasFlag(InputNetFlags.Select) ||
|
||||
newInput.HasFlag(InputNetFlags.Use) ||
|
||||
newInput.HasFlag(InputNetFlags.Health) ||
|
||||
newInput.HasFlag(InputNetFlags.Grab))
|
||||
{
|
||||
newInteract = msg.ReadUInt16();
|
||||
}
|
||||
|
||||
//if (AllowInput)
|
||||
//{
|
||||
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
|
||||
{
|
||||
NetInputMem newMem = new NetInputMem();
|
||||
newMem.states = newInput;
|
||||
newMem.intAim = newAim;
|
||||
newMem.interact = newInteract;
|
||||
|
||||
newMem.networkUpdateID = (ushort)(networkUpdateID - i);
|
||||
|
||||
memInput.Insert(i, newMem);
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
if (NetIdUtils.IdMoreRecent(networkUpdateID, LastNetworkUpdateID))
|
||||
{
|
||||
LastNetworkUpdateID = networkUpdateID;
|
||||
}
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
|
||||
//we'll make the server drop down to 30 inputs for good measure
|
||||
memInput.RemoveRange(30, memInput.Count - 30);
|
||||
}
|
||||
break;
|
||||
|
||||
case ClientNetObject.ENTITY_STATE:
|
||||
int eventType = msg.ReadRangedInteger(0,3);
|
||||
switch (eventType)
|
||||
{
|
||||
case 0:
|
||||
Inventory.ServerRead(type, msg, c);
|
||||
break;
|
||||
case 1:
|
||||
bool doingCPR = msg.ReadBoolean();
|
||||
if (c.Character != this)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
AnimController.Anim = doingCPR ? AnimController.Animation.CPR : AnimController.Animation.None;
|
||||
break;
|
||||
case 2:
|
||||
if (c.Character != this)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsUnconscious)
|
||||
{
|
||||
var causeOfDeath = CharacterHealth.GetCauseOfDeath();
|
||||
Kill(causeOfDeath.First, causeOfDeath.Second);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
msg.ReadPadBits();
|
||||
}
|
||||
|
||||
public virtual void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
if (extraData != null)
|
||||
{
|
||||
switch ((NetEntityEvent.Type)extraData[0])
|
||||
{
|
||||
case NetEntityEvent.Type.InventoryState:
|
||||
msg.WriteRangedInteger(0, 3, 0);
|
||||
Inventory.ClientWrite(msg, extraData);
|
||||
break;
|
||||
case NetEntityEvent.Type.Control:
|
||||
msg.WriteRangedInteger(0, 3, 1);
|
||||
Client owner = ((Client)extraData[1]);
|
||||
msg.Write(owner == null ? (byte)0 : owner.ID);
|
||||
break;
|
||||
case NetEntityEvent.Type.Status:
|
||||
msg.WriteRangedInteger(0, 3, 2);
|
||||
WriteStatus(msg);
|
||||
break;
|
||||
case NetEntityEvent.Type.UpdateSkills:
|
||||
msg.WriteRangedInteger(0, 3, 3);
|
||||
if (Info?.Job == null)
|
||||
{
|
||||
msg.Write((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)Info.Job.Skills.Count);
|
||||
foreach (Skill skill in Info.Job.Skills)
|
||||
{
|
||||
msg.Write(skill.Identifier);
|
||||
msg.Write(skill.Level);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");
|
||||
break;
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(ID);
|
||||
|
||||
NetBuffer tempBuffer = new NetBuffer();
|
||||
|
||||
if (this == c.Character)
|
||||
{
|
||||
tempBuffer.Write(true);
|
||||
if (LastNetworkUpdateID < memInput.Count + 1)
|
||||
{
|
||||
tempBuffer.Write((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write(false);
|
||||
|
||||
bool aiming = false;
|
||||
bool use = false;
|
||||
bool attack = false;
|
||||
|
||||
if (IsRemotePlayer)
|
||||
{
|
||||
aiming = dequeuedInput.HasFlag(InputNetFlags.Aim);
|
||||
use = dequeuedInput.HasFlag(InputNetFlags.Use);
|
||||
|
||||
attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
|
||||
}
|
||||
else if (keys != null)
|
||||
{
|
||||
aiming = keys[(int)InputType.Aim].GetHeldQueue;
|
||||
use = keys[(int)InputType.Use].GetHeldQueue;
|
||||
|
||||
attack = keys[(int)InputType.Attack].GetHeldQueue;
|
||||
|
||||
networkUpdateSent = true;
|
||||
}
|
||||
|
||||
tempBuffer.Write(aiming);
|
||||
tempBuffer.Write(use);
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching);
|
||||
}
|
||||
|
||||
AttackContext currentContext = GetAttackContext();
|
||||
// TODO: do we need to filter the attack target here?
|
||||
bool hasAttackLimb = AnimController.Limbs.Any(l => l != null && l.attack != null && l.attack.IsValidContext(currentContext));
|
||||
tempBuffer.Write(hasAttackLimb);
|
||||
if (hasAttackLimb) tempBuffer.Write(attack);
|
||||
|
||||
if (aiming)
|
||||
{
|
||||
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
|
||||
tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
|
||||
}
|
||||
tempBuffer.Write(IsRagdolled);
|
||||
|
||||
tempBuffer.Write(AnimController.TargetDir == Direction.Right);
|
||||
}
|
||||
|
||||
if (SelectedCharacter != null || SelectedConstruction != null)
|
||||
{
|
||||
tempBuffer.Write(true);
|
||||
tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : SelectedConstruction.ID);
|
||||
if (SelectedCharacter != null)
|
||||
{
|
||||
tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write(false);
|
||||
}
|
||||
|
||||
tempBuffer.Write(SimPosition.X);
|
||||
tempBuffer.Write(SimPosition.Y);
|
||||
tempBuffer.Write(AnimController.Collider.Rotation);
|
||||
|
||||
WriteStatus(tempBuffer);
|
||||
|
||||
tempBuffer.WritePadBits();
|
||||
|
||||
msg.Write((byte)tempBuffer.LengthBytes);
|
||||
msg.Write(tempBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteStatus(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Client attempted to write character status to a networked message");
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write(IsDead);
|
||||
if (IsDead)
|
||||
{
|
||||
msg.WriteRangedInteger(0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1, (int)CauseOfDeath.Type);
|
||||
if (CauseOfDeath.Type == CauseOfDeathType.Affliction)
|
||||
{
|
||||
msg.WriteRangedInteger(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(CauseOfDeath.Affliction));
|
||||
}
|
||||
|
||||
if (AnimController?.LimbJoints == null)
|
||||
{
|
||||
//0 limbs severed
|
||||
msg.Write((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<int> severedJointIndices = new List<int>();
|
||||
for (int i = 0; i < AnimController.LimbJoints.Length; i++)
|
||||
{
|
||||
if (AnimController.LimbJoints[i] != null && AnimController.LimbJoints[i].IsSevered)
|
||||
{
|
||||
severedJointIndices.Add(i);
|
||||
}
|
||||
}
|
||||
msg.Write((byte)severedJointIndices.Count);
|
||||
foreach (int jointIndex in severedJointIndices)
|
||||
{
|
||||
msg.Write((byte)jointIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterHealth.ServerWrite(msg);
|
||||
msg.Write(IsRagdolled);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteSpawnData(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Server == null) { return; }
|
||||
|
||||
msg.Write(Info == null);
|
||||
msg.Write(ID);
|
||||
msg.Write(ConfigPath);
|
||||
msg.Write(seed);
|
||||
|
||||
if (Removed)
|
||||
{
|
||||
msg.Write(0.0f);
|
||||
msg.Write(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(WorldPosition.X);
|
||||
msg.Write(WorldPosition.Y);
|
||||
}
|
||||
msg.Write(Enabled);
|
||||
|
||||
//character with no characterinfo (e.g. some monster)
|
||||
if (Info == null) { return; }
|
||||
|
||||
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||
if (ownerClient != null)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write(ownerClient.ID);
|
||||
}
|
||||
else if (GameMain.Server.Character == this)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(false);
|
||||
}
|
||||
|
||||
msg.Write(TeamID);
|
||||
msg.Write(this is AICharacter);
|
||||
info.ServerWrite(msg);
|
||||
}
|
||||
partial void UpdateNetInput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Affliction
|
||||
{
|
||||
public readonly AfflictionPrefab Prefab;
|
||||
|
||||
public float Strength;
|
||||
|
||||
public float DamagePerSecond;
|
||||
public float DamagePerSecondTimer;
|
||||
public float PreviousVitalityDecrease;
|
||||
|
||||
public float StrengthDiminishMultiplier = 1.0f;
|
||||
public Affliction MultiplierSource;
|
||||
|
||||
/// <summary>
|
||||
/// Which character gave this affliction
|
||||
/// </summary>
|
||||
public Character Source;
|
||||
|
||||
public Affliction(AfflictionPrefab prefab, float strength)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Strength = strength;
|
||||
}
|
||||
|
||||
public Affliction CreateMultiplied(float multiplier)
|
||||
{
|
||||
return Prefab.Instantiate(Strength * multiplier, Source);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Affliction (" + Prefab.Name + ")";
|
||||
}
|
||||
|
||||
public float GetVitalityDecrease(CharacterHealth characterHealth)
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxStrength - currentEffect.MinStrength <= 0.0f) return 0.0f;
|
||||
|
||||
float currVitalityDecrease = MathHelper.Lerp(
|
||||
currentEffect.MinVitalityDecrease,
|
||||
currentEffect.MaxVitalityDecrease,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
|
||||
if (currentEffect.MultiplyByMaxVitality) currVitalityDecrease *= characterHealth == null ? 100.0f : characterHealth.MaxVitality;
|
||||
|
||||
return currVitalityDecrease;
|
||||
}
|
||||
|
||||
public float GetScreenDistortStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength <= 0.0f) return 0.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinScreenDistortStrength,
|
||||
currentEffect.MaxScreenDistortStrength,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public float GetRadialDistortStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength <= 0.0f) return 0.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinRadialDistortStrength,
|
||||
currentEffect.MaxRadialDistortStrength,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public float GetChromaticAberrationStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength <= 0.0f) return 0.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinChromaticAberrationStrength,
|
||||
currentEffect.MaxChromaticAberrationStrength,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public float GetScreenBlurStrength()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength <= 0.0f) return 0.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinScreenBlurStrength,
|
||||
currentEffect.MaxScreenBlurStrength,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public void CalculateDamagePerSecond(float currentVitalityDecrease)
|
||||
{
|
||||
DamagePerSecond = Math.Max(DamagePerSecond, currentVitalityDecrease - PreviousVitalityDecrease);
|
||||
if (DamagePerSecondTimer >= 1.0f)
|
||||
{
|
||||
DamagePerSecond = currentVitalityDecrease - PreviousVitalityDecrease;
|
||||
PreviousVitalityDecrease = currentVitalityDecrease;
|
||||
DamagePerSecondTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetResistance(string afflictionId)
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 0.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 0.0f;
|
||||
if (currentEffect.MaxResistance - currentEffect.MinResistance <= 0.0f) return 0.0f;
|
||||
if (afflictionId != currentEffect.ResistanceFor) return 0.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinResistance,
|
||||
currentEffect.MaxResistance,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public float GetSpeedMultiplier()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 1.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 1.0f;
|
||||
if (currentEffect.MaxSpeedMultiplier - currentEffect.MinSpeedMultiplier <= 0.0f) return 1.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinSpeedMultiplier,
|
||||
currentEffect.MaxSpeedMultiplier,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
|
||||
public virtual void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return;
|
||||
|
||||
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
|
||||
{
|
||||
Strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
|
||||
}
|
||||
else // Reduce strengthening of afflictions if resistant
|
||||
{
|
||||
Strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
|
||||
}
|
||||
|
||||
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
|
||||
{
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, characterHealth.Character);
|
||||
}
|
||||
if (targetLimb != null && statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, targetLimb);
|
||||
}
|
||||
if (targetLimb != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, targetLimb.character, targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets);
|
||||
statusEffect.Apply(ActionType.OnActive, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AfflictionBleeding : Affliction
|
||||
{
|
||||
public AfflictionBleeding(AfflictionPrefab prefab, float strength) :
|
||||
base(prefab, strength)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
characterHealth.BloodlossAmount += Strength * (1.0f / 60.0f) * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-74
@@ -1,7 +1,4 @@
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -19,7 +16,7 @@ namespace Barotrauma
|
||||
|
||||
private InfectionState state;
|
||||
|
||||
private Limb huskAppendage;
|
||||
private List<Limb> huskAppendage;
|
||||
|
||||
public InfectionState State
|
||||
{
|
||||
@@ -59,30 +56,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMessages(float prevStrength, Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
if (Strength < Prefab.MaxStrength * 0.5f)
|
||||
{
|
||||
if (prevStrength % 10.0f > 0.05f && Strength % 10.0f < 0.05f)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("HuskDormant"), Color.Red);
|
||||
}
|
||||
}
|
||||
else if (Strength < Prefab.MaxStrength)
|
||||
{
|
||||
if (state == InfectionState.Dormant && Character.Controlled == character)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), Color.Red);
|
||||
}
|
||||
}
|
||||
else if (state != InfectionState.Active && Character.Controlled == character)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("HuskActivate").Replace("[Attack]", GameMain.Config.KeyBind(InputType.Attack).ToString()),
|
||||
Color.Red);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
partial void UpdateMessages(float prevStrength, Character character);
|
||||
|
||||
private void UpdateDormantState(float deltaTime, Character character)
|
||||
{
|
||||
@@ -125,45 +99,44 @@ namespace Barotrauma
|
||||
private void ActivateHusk(Character character)
|
||||
{
|
||||
character.NeedsAir = false;
|
||||
AttachHuskAppendage(character);
|
||||
if (huskAppendage == null)
|
||||
{
|
||||
huskAppendage = AttachHuskAppendage(character);
|
||||
character.SetStun(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachHuskAppendage(Character character)
|
||||
public static List<Limb> AttachHuskAppendage(Character character, Ragdoll ragdoll = null)
|
||||
{
|
||||
//husk appendage already created, don't do anything
|
||||
if (huskAppendage != null) return;
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine("Content", "Characters", "Human", "Huskappendage.xml"));
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
var limbElement = doc.Root.Element("limb");
|
||||
if (limbElement == null)
|
||||
var huskDoc = XMLExtensions.TryLoadXml(Character.GetConfigFile("humanhusk"));
|
||||
string pathToAppendage = huskDoc.Root.Element("huskappendage").GetAttributeString("path", string.Empty);
|
||||
XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage);
|
||||
if (doc == null || doc.Root == null) { return null; }
|
||||
if (ragdoll == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in huskappendage.xml - limb element not found");
|
||||
return;
|
||||
ragdoll = character.AnimController;
|
||||
}
|
||||
|
||||
var jointElement = doc.Root.Element("joint");
|
||||
if (jointElement == null)
|
||||
if (ragdoll.Dir < 1.0f)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in huskappendage.xml - joint element not found");
|
||||
return;
|
||||
ragdoll.Flip();
|
||||
}
|
||||
|
||||
character.SetStun(0.5f);
|
||||
if (character.AnimController.Dir < 1.0f)
|
||||
var huskAppendages = new List<Limb>();
|
||||
var limbElements = doc.Root.Elements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);
|
||||
foreach (var jointElement in doc.Root.Elements("joint"))
|
||||
{
|
||||
character.AnimController.Flip();
|
||||
if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out XElement limbElement))
|
||||
{
|
||||
JointParams jointParams = new JointParams(jointElement, ragdoll.RagdollParams);
|
||||
Limb attachLimb = ragdoll.Limbs[jointParams.Limb1];
|
||||
Limb huskAppendage = new Limb(ragdoll, character, new LimbParams(limbElement, ragdoll.RagdollParams));
|
||||
huskAppendage.body.Submarine = character.Submarine;
|
||||
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
|
||||
ragdoll.AddLimb(huskAppendage);
|
||||
ragdoll.AddJoint(jointParams);
|
||||
huskAppendages.Add(huskAppendage);
|
||||
}
|
||||
}
|
||||
|
||||
var torso = character.AnimController.GetLimb(LimbType.Torso);
|
||||
|
||||
huskAppendage = new Limb(character.AnimController, character, new LimbParams(limbElement, character.AnimController.RagdollParams));
|
||||
huskAppendage.body.Submarine = character.Submarine;
|
||||
huskAppendage.body.SetTransform(torso.SimPosition, torso.Rotation);
|
||||
|
||||
character.AnimController.AddLimb(huskAppendage);
|
||||
character.AnimController.AddJoint(jointElement);
|
||||
return huskAppendages;
|
||||
}
|
||||
|
||||
private void DeactivateHusk(Character character)
|
||||
@@ -175,8 +148,7 @@ namespace Barotrauma
|
||||
private void RemoveHuskAppendage(Character character)
|
||||
{
|
||||
if (huskAppendage == null) return;
|
||||
|
||||
character.AnimController.RemoveLimb(huskAppendage);
|
||||
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
|
||||
huskAppendage = null;
|
||||
}
|
||||
|
||||
@@ -189,7 +161,7 @@ namespace Barotrauma
|
||||
|
||||
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
if (GameMain.Client != null) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (Strength < Prefab.MaxStrength * 0.5f || character.Removed) { return; }
|
||||
|
||||
//don't turn the character into a husk if any of its limbs are severed
|
||||
@@ -210,25 +182,24 @@ namespace Barotrauma
|
||||
character.Enabled = false;
|
||||
Entity.Spawner.AddToRemoveQueue(character);
|
||||
|
||||
var characterFiles = GameMain.Instance.GetFilesOfType(ContentType.Character);
|
||||
var configFile = characterFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f)?.ToLowerInvariant() == "humanhusk");
|
||||
var configFile = Character.GetConfigFile("humanhusk");
|
||||
|
||||
if (string.IsNullOrEmpty(configFile))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - humanhusk config file not found.");
|
||||
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile);
|
||||
if (doc?.Root == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - humanhusk config file ("+configFile+") could not be read.");
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
//XDocument doc = XMLExtensions.TryLoadXml(configFile);
|
||||
//if (doc?.Root == null)
|
||||
//{
|
||||
// DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file ("+configFile+") could not be read.");
|
||||
// yield return CoroutineStatus.Success;
|
||||
//}
|
||||
|
||||
character.Info.Ragdoll = null;
|
||||
character.Info.SourceElement = doc.Root;
|
||||
var husk = Character.Create(configFile, character.WorldPosition, character.Info.Name, character.Info, false, true);
|
||||
//character.Info.Ragdoll = null;
|
||||
//character.Info.SourceElement = doc.Root;
|
||||
var husk = Character.Create(configFile, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true);
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
@@ -0,0 +1,355 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class CPRSettings
|
||||
{
|
||||
public static float ReviveChancePerSkill { get; private set; }
|
||||
public static float ReviveChanceExponent { get; private set; }
|
||||
public static float ReviveChanceMin { get; private set; }
|
||||
public static float ReviveChanceMax { get; private set; }
|
||||
public static float StabilizationPerSkill { get; private set; }
|
||||
public static float StabilizationMin { get; private set; }
|
||||
public static float StabilizationMax { get; private set; }
|
||||
public static float DamageSkillThreshold { get; private set; }
|
||||
public static float DamageSkillMultiplier { get; private set; }
|
||||
|
||||
public static void Load(XElement element)
|
||||
{
|
||||
ReviveChancePerSkill = Math.Max(element.GetAttributeFloat("revivechanceperskill", 0.01f), 0.0f);
|
||||
ReviveChanceExponent = Math.Max(element.GetAttributeFloat("revivechanceexponent", 2.0f), 0.0f);
|
||||
ReviveChanceMin = MathHelper.Clamp(element.GetAttributeFloat("revivechancemin", 0.05f), 0.0f, 1.0f);
|
||||
ReviveChanceMax = MathHelper.Clamp(element.GetAttributeFloat("revivechancemax", 0.9f), ReviveChanceMin, 1.0f);
|
||||
|
||||
StabilizationPerSkill = Math.Max(element.GetAttributeFloat("stabilizationperskill", 0.01f), 0.0f);
|
||||
StabilizationMin = MathHelper.Max(element.GetAttributeFloat("stabilizationmin", 0.05f), 0.0f);
|
||||
StabilizationMax = MathHelper.Max(element.GetAttributeFloat("stabilizationmax", 2.0f), StabilizationMin);
|
||||
|
||||
DamageSkillThreshold = MathHelper.Clamp(element.GetAttributeFloat("damageskillthreshold", 40.0f), 0.0f, 100.0f);
|
||||
DamageSkillMultiplier = MathHelper.Clamp(element.GetAttributeFloat("damageskillmultiplier", 0.1f), 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
class AfflictionPrefab
|
||||
{
|
||||
public class Effect
|
||||
{
|
||||
//this effect is applied when the strength is within this range
|
||||
public float MinStrength, MaxStrength;
|
||||
|
||||
public readonly float MinVitalityDecrease = 0.0f;
|
||||
public readonly float MaxVitalityDecrease = 0.0f;
|
||||
|
||||
//how much the strength of the affliction changes per second
|
||||
public readonly float StrengthChange = 0.0f;
|
||||
|
||||
public readonly bool MultiplyByMaxVitality;
|
||||
|
||||
public float MinScreenBlurStrength, MaxScreenBlurStrength;
|
||||
public float MinScreenDistortStrength, MaxScreenDistortStrength;
|
||||
public float MinRadialDistortStrength, MaxRadialDistortStrength;
|
||||
public float MinChromaticAberrationStrength, MaxChromaticAberrationStrength;
|
||||
public float MinSpeedMultiplier, MaxSpeedMultiplier;
|
||||
public float MinBuffMultiplier, MaxBuffMultiplier;
|
||||
|
||||
public float MinResistance, MaxResistance;
|
||||
public string ResistanceFor;
|
||||
public string DialogFlag;
|
||||
|
||||
//statuseffects applied on the character when the affliction is active
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
|
||||
public Effect(XElement element, string parentDebugName)
|
||||
{
|
||||
MinStrength = element.GetAttributeFloat("minstrength", 0);
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 0);
|
||||
|
||||
MultiplyByMaxVitality = element.GetAttributeBool("multiplybymaxvitality", false);
|
||||
|
||||
MinVitalityDecrease = element.GetAttributeFloat("minvitalitydecrease", 0.0f);
|
||||
MaxVitalityDecrease = element.GetAttributeFloat("maxvitalitydecrease", 0.0f);
|
||||
MaxVitalityDecrease = Math.Max(MinVitalityDecrease, MaxVitalityDecrease);
|
||||
|
||||
MinScreenDistortStrength = element.GetAttributeFloat("minscreendistort", 0.0f);
|
||||
MaxScreenDistortStrength = element.GetAttributeFloat("maxscreendistort", 0.0f);
|
||||
MaxScreenDistortStrength = Math.Max(MinScreenDistortStrength, MaxScreenDistortStrength);
|
||||
|
||||
MinRadialDistortStrength = element.GetAttributeFloat("minradialdistort", 0.0f);
|
||||
MaxRadialDistortStrength = element.GetAttributeFloat("maxradialdistort", 0.0f);
|
||||
MaxRadialDistortStrength = Math.Max(MinRadialDistortStrength, MaxRadialDistortStrength);
|
||||
|
||||
MinChromaticAberrationStrength = element.GetAttributeFloat("minchromaticaberration", 0.0f);
|
||||
MaxChromaticAberrationStrength = element.GetAttributeFloat("maxchromaticaberration", 0.0f);
|
||||
MaxChromaticAberrationStrength = Math.Max(MinChromaticAberrationStrength, MaxChromaticAberrationStrength);
|
||||
|
||||
MinScreenBlurStrength = element.GetAttributeFloat("minscreenblur", 0.0f);
|
||||
MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f);
|
||||
MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength);
|
||||
|
||||
ResistanceFor = element.GetAttributeString("resistancefor", "");
|
||||
MinResistance = element.GetAttributeFloat("minresistance", 0.0f);
|
||||
MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f);
|
||||
MaxResistance = Math.Max(MinResistance, MaxResistance);
|
||||
|
||||
MinSpeedMultiplier = element.GetAttributeFloat("minspeedmultiplier", 1.0f);
|
||||
MaxSpeedMultiplier = element.GetAttributeFloat("maxspeedmultiplier", 1.0f);
|
||||
MaxSpeedMultiplier = Math.Max(MinSpeedMultiplier, MaxSpeedMultiplier);
|
||||
|
||||
MinBuffMultiplier = element.GetAttributeFloat("minmultiplier", 1.0f);
|
||||
MaxBuffMultiplier = element.GetAttributeFloat("maxmultiplier", 1.0f);
|
||||
MaxBuffMultiplier = Math.Max(MinBuffMultiplier, MaxBuffMultiplier);
|
||||
|
||||
DialogFlag = element.GetAttributeString("dialogflag", "");
|
||||
|
||||
StrengthChange = element.GetAttributeFloat("strengthchange", 0.0f);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AfflictionPrefab InternalDamage;
|
||||
public static AfflictionPrefab Bleeding;
|
||||
public static AfflictionPrefab Burn;
|
||||
public static AfflictionPrefab OxygenLow;
|
||||
public static AfflictionPrefab Bloodloss;
|
||||
public static AfflictionPrefab Pressure;
|
||||
public static AfflictionPrefab Stun;
|
||||
public static AfflictionPrefab Husk;
|
||||
|
||||
public static List<AfflictionPrefab> List = new List<AfflictionPrefab>();
|
||||
|
||||
//Arbitrary string that is used to identify the type of the affliction.
|
||||
//Afflictions with the same type stack up, and items may be configured to cure specific types of afflictions.
|
||||
public readonly string AfflictionType;
|
||||
|
||||
//Does the affliction affect a specific limb or the whole character
|
||||
public readonly bool LimbSpecific;
|
||||
|
||||
//If not a limb-specific affliction, which limb is the indicator shown on in the health menu
|
||||
//(e.g. mental health problems on head, lack of oxygen on torso...)
|
||||
public readonly LimbType IndicatorLimb;
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly string Name, Description;
|
||||
public readonly bool IsBuff;
|
||||
|
||||
public readonly string CauseOfDeathDescription, SelfCauseOfDeathDescription;
|
||||
|
||||
//how high the strength has to be for the affliction to take affect
|
||||
public readonly float ActivationThreshold = 0.0f;
|
||||
//how high the strength has to be for the affliction icon to be shown in the UI
|
||||
public readonly float ShowIconThreshold = 0.0f;
|
||||
public readonly float MaxStrength = 100.0f;
|
||||
|
||||
public float BurnOverlayAlpha;
|
||||
public float DamageOverlayAlpha;
|
||||
|
||||
//steam achievement given when the affliction is removed from the controlled character
|
||||
public readonly string AchievementOnRemoved;
|
||||
|
||||
public readonly Sprite Icon;
|
||||
public readonly Color IconColor;
|
||||
|
||||
private List<Effect> effects = new List<Effect>();
|
||||
|
||||
private Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();
|
||||
|
||||
private readonly string typeName;
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
public Dictionary<string, float> TreatmentSuitability
|
||||
{
|
||||
get { return treatmentSuitability; }
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "internaldamage":
|
||||
List.Add(InternalDamage = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "bleeding":
|
||||
List.Add(Bleeding = new AfflictionPrefab(element, typeof(AfflictionBleeding)));
|
||||
break;
|
||||
case "burn":
|
||||
List.Add(Burn = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "oxygenlow":
|
||||
List.Add(OxygenLow = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "bloodloss":
|
||||
List.Add(Bloodloss = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "pressure":
|
||||
List.Add(Pressure = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "stun":
|
||||
List.Add(Stun = new AfflictionPrefab(element, typeof(Affliction)));
|
||||
break;
|
||||
case "husk":
|
||||
case "afflictionhusk":
|
||||
List.Add(Husk = new AfflictionPrefab(element, typeof(AfflictionHusk)));
|
||||
break;
|
||||
case "cprsettings":
|
||||
CPRSettings.Load(element);
|
||||
break;
|
||||
default:
|
||||
List.Add(new AfflictionPrefab(element));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (InternalDamage == null) DebugConsole.ThrowError("Affliction \"Internal Damage\" not defined in the affliction prefabs.");
|
||||
if (Bleeding == null) DebugConsole.ThrowError("Affliction \"Bleeding\" not defined in the affliction prefabs.");
|
||||
if (Burn == null) DebugConsole.ThrowError("Affliction \"Burn\" not defined in the affliction prefabs.");
|
||||
if (OxygenLow == null) DebugConsole.ThrowError("Affliction \"OxygenLow\" not defined in the affliction prefabs.");
|
||||
if (Bloodloss == null) DebugConsole.ThrowError("Affliction \"Bloodloss\" not defined in the affliction prefabs.");
|
||||
if (Pressure == null) DebugConsole.ThrowError("Affliction \"Pressure\" not defined in the affliction prefabs.");
|
||||
if (Stun == null) DebugConsole.ThrowError("Affliction \"Stun\" not defined in the affliction prefabs.");
|
||||
if (Husk == null) DebugConsole.ThrowError("Affliction \"Husk\" not defined in the affliction prefabs.");
|
||||
}
|
||||
|
||||
public AfflictionPrefab(XElement element, Type type = null)
|
||||
{
|
||||
typeName = type == null ? element.Name.ToString() : type.Name;
|
||||
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
|
||||
AfflictionType = element.GetAttributeString("type", "");
|
||||
Name = TextManager.Get("AfflictionName." + Identifier, true) ?? element.GetAttributeString("name", "");
|
||||
Description = TextManager.Get("AfflictionDescription." + Identifier, true) ?? element.GetAttributeString("description", "");
|
||||
IsBuff = element.GetAttributeBool("isbuff", false);
|
||||
|
||||
LimbSpecific = element.GetAttributeBool("limbspecific", false);
|
||||
if (!LimbSpecific)
|
||||
{
|
||||
string indicatorLimbName = element.GetAttributeString("indicatorlimb", "Torso");
|
||||
if (!Enum.TryParse(indicatorLimbName, out IndicatorLimb))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in affliction prefab " + Name + " - limb type \"" + indicatorLimbName + "\" not found.");
|
||||
}
|
||||
}
|
||||
|
||||
ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
|
||||
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", ActivationThreshold);
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
|
||||
|
||||
CauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeath." + Identifier, true) ?? element.GetAttributeString("causeofdeathdescription", "");
|
||||
SelfCauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeathSelf." + Identifier, true) ?? element.GetAttributeString("selfcauseofdeathdescription", "");
|
||||
|
||||
AchievementOnRemoved = element.GetAttributeString("achievementonremoved", "");
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "icon":
|
||||
Icon = new Sprite(subElement);
|
||||
IconColor = subElement.GetAttributeColor("color", Color.White);
|
||||
break;
|
||||
case "effect":
|
||||
effects.Add(new Effect(subElement, Name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
type = Type.GetType("Barotrauma." + typeName, true, true);
|
||||
if (type == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an affliction class of the type \"" + typeName + "\".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an affliction class of the type \"" + typeName + "\".");
|
||||
return;
|
||||
}
|
||||
|
||||
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "AfflictionPrefab (" + Name + ")";
|
||||
}
|
||||
|
||||
public Affliction Instantiate(float strength, Character source = null)
|
||||
{
|
||||
object instance = null;
|
||||
try
|
||||
{
|
||||
instance = constructor.Invoke(new object[] { this, strength });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
|
||||
}
|
||||
Affliction affliction = instance as Affliction;
|
||||
affliction.Source = source;
|
||||
return affliction;
|
||||
}
|
||||
|
||||
public Effect GetActiveEffect(float currentStrength)
|
||||
{
|
||||
foreach (Effect effect in effects)
|
||||
{
|
||||
if (currentStrength > effect.MinStrength && currentStrength <= effect.MaxStrength) return effect;
|
||||
}
|
||||
|
||||
//if above the strength range of all effects, use the highest strength effect
|
||||
Effect strongestEffect = null;
|
||||
float largestStrength = currentStrength;
|
||||
foreach (Effect effect in effects)
|
||||
{
|
||||
if (currentStrength > effect.MaxStrength &&
|
||||
(strongestEffect == null || effect.MaxStrength > largestStrength))
|
||||
{
|
||||
strongestEffect = effect;
|
||||
largestStrength = effect.MaxStrength;
|
||||
}
|
||||
}
|
||||
return strongestEffect;
|
||||
}
|
||||
|
||||
public float GetTreatmentSuitability(Item item)
|
||||
{
|
||||
if (item == null || !treatmentSuitability.ContainsKey(item.Prefab.Identifier.ToLowerInvariant()))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return treatmentSuitability[item.Prefab.Identifier.ToLowerInvariant()];
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AfflictionPsychosis : Affliction
|
||||
{
|
||||
|
||||
public AfflictionPsychosis(AfflictionPrefab prefab, float strength) : base(prefab, strength)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
UpdateProjSpecific(characterHealth, targetLimb, deltaTime);
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(CharacterHealth characterHealth, Limb targetLimb, float deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BuffDurationIncrease : Affliction
|
||||
{
|
||||
public BuffDurationIncrease(AfflictionPrefab prefab, float strength) : base(prefab, strength)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
|
||||
var afflictions = characterHealth.GetAllAfflictions();
|
||||
|
||||
if (Strength <= 0)
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (!affliction.Prefab.IsBuff || affliction == this || affliction.MultiplierSource != this) continue;
|
||||
affliction.MultiplierSource = null;
|
||||
affliction.StrengthDiminishMultiplier = 1f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (!affliction.Prefab.IsBuff || affliction == this || affliction.MultiplierSource == this) continue;
|
||||
float multiplier = GetDiminishMultiplier();
|
||||
if (affliction.StrengthDiminishMultiplier < multiplier) continue;
|
||||
|
||||
affliction.MultiplierSource = this;
|
||||
affliction.StrengthDiminishMultiplier = multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float GetDiminishMultiplier()
|
||||
{
|
||||
if (Strength < Prefab.ActivationThreshold) return 1.0f;
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
|
||||
if (currentEffect == null) return 1.0f;
|
||||
|
||||
return MathHelper.Lerp(
|
||||
currentEffect.MinBuffMultiplier,
|
||||
currentEffect.MaxBuffMultiplier,
|
||||
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
{
|
||||
return Afflictions.FindAll(a => a.Prefab.AfflictionType == afflictionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float InsufficientOxygenThreshold = 30.0f;
|
||||
const float LowOxygenThreshold = 50.0f;
|
||||
@@ -339,7 +339,20 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
AddAffliction(affliction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetResistance(string resistanceId)
|
||||
{
|
||||
float resistance = 0.0f;
|
||||
for (int i = 0; i < afflictions.Count; i++)
|
||||
{
|
||||
if (!afflictions[i].Prefab.IsBuff) continue;
|
||||
float temp = afflictions[i].GetResistance(resistanceId);
|
||||
if (temp > resistance) resistance = temp;
|
||||
}
|
||||
|
||||
return resistance;
|
||||
}
|
||||
|
||||
public void ReduceAffliction(Limb targetLimb, string affliction, float amount)
|
||||
@@ -459,7 +472,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (newAffliction.Prefab == affliction.Prefab)
|
||||
{
|
||||
affliction.Strength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + newAffliction.Strength * (100.0f / MaxVitality));
|
||||
affliction.Strength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
|
||||
affliction.Source = newAffliction.Source;
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
@@ -469,11 +482,12 @@ namespace Barotrauma
|
||||
|
||||
//create a new instance of the affliction to make sure we don't use the same instance for multiple characters
|
||||
//or modify the affliction instance of an Attack or a StatusEffect
|
||||
|
||||
var copyAffliction = newAffliction.Prefab.Instantiate(
|
||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality)),
|
||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))),
|
||||
newAffliction.Source);
|
||||
limbHealth.Afflictions.Add(copyAffliction);
|
||||
|
||||
Character.HealthUpdateInterval = 0.0f;
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
@@ -486,11 +500,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) return;
|
||||
if (!Character.NeedsAir && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
// Currently only human can get the husk infection.
|
||||
if (newAffliction.Prefab == AfflictionPrefab.Husk && Character.SpeciesName.ToLowerInvariant() != "human") { return; }
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (newAffliction.Prefab == affliction.Prefab)
|
||||
{
|
||||
float newStrength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + newAffliction.Strength * (100.0f / MaxVitality));
|
||||
float newStrength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier))));
|
||||
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
|
||||
affliction.Strength = newStrength;
|
||||
affliction.Source = newAffliction.Source;
|
||||
@@ -503,13 +519,17 @@ namespace Barotrauma
|
||||
//create a new instance of the affliction to make sure we don't use the same instance for multiple characters
|
||||
//or modify the affliction instance of an Attack or a StatusEffect
|
||||
afflictions.Add(newAffliction.Prefab.Instantiate(
|
||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality)),
|
||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))),
|
||||
source: newAffliction.Source));
|
||||
|
||||
Character.HealthUpdateInterval = 0.0f;
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
}
|
||||
|
||||
partial void UpdateLimbAfflictionOverlays();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
UpdateOxygen(deltaTime);
|
||||
@@ -533,39 +553,29 @@ namespace Barotrauma
|
||||
{
|
||||
UpdateBleedingProjSpecific((AfflictionBleeding)affliction, targetLimb, deltaTime);
|
||||
}
|
||||
Character.SpeedMultiplier = affliction.GetSpeedMultiplier();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = afflictions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (irremovableAfflictions.Contains(afflictions[i])) continue;
|
||||
if (afflictions[i].Strength <= 0.0f)
|
||||
var affliction = afflictions[i];
|
||||
if (irremovableAfflictions.Contains(affliction)) continue;
|
||||
if (affliction.Strength <= 0.0f)
|
||||
{
|
||||
SteamAchievementManager.OnAfflictionRemoved(afflictions[i], Character);
|
||||
SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
|
||||
afflictions.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < afflictions.Count; i++)
|
||||
{
|
||||
afflictions[i].Update(this, null, deltaTime);
|
||||
afflictions[i].DamagePerSecondTimer += deltaTime;
|
||||
var affliction = afflictions[i];
|
||||
affliction.Update(this, null, deltaTime);
|
||||
affliction.DamagePerSecondTimer += deltaTime;
|
||||
Character.SpeedMultiplier = affliction.GetSpeedMultiplier();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
limb.BurnOverlayStrength = 0.0f;
|
||||
limb.DamageOverlayStrength = 0.0f;
|
||||
if (limbHealths[limb.HealthIndex].Afflictions.Count == 0) continue;
|
||||
foreach (Affliction a in limbHealths[limb.HealthIndex].Afflictions)
|
||||
{
|
||||
limb.BurnOverlayStrength += a.Strength / a.Prefab.MaxStrength * a.Prefab.BurnOverlayAlpha;
|
||||
limb.DamageOverlayStrength += a.Strength / a.Prefab.MaxStrength * a.Prefab.DamageOverlayAlpha;
|
||||
}
|
||||
limb.BurnOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count;
|
||||
limb.DamageOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count;
|
||||
}
|
||||
#endif
|
||||
|
||||
UpdateLimbAfflictionOverlays();
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) Kill();
|
||||
@@ -598,6 +608,8 @@ namespace Barotrauma
|
||||
Vitality = MaxVitality;
|
||||
if (Unkillable) { return; }
|
||||
|
||||
float damageResistanceMultiplier = 1f - GetResistance("damage");
|
||||
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
{
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
@@ -611,6 +623,7 @@ namespace Barotrauma
|
||||
{
|
||||
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[affliction.Prefab.AfflictionType.ToLowerInvariant()];
|
||||
}
|
||||
vitalityDecrease *= damageResistanceMultiplier;
|
||||
Vitality -= vitalityDecrease;
|
||||
affliction.CalculateDamagePerSecond(vitalityDecrease);
|
||||
}
|
||||
@@ -619,6 +632,7 @@ namespace Barotrauma
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
float vitalityDecrease = affliction.GetVitalityDecrease(this);
|
||||
vitalityDecrease *= damageResistanceMultiplier;
|
||||
Vitality -= vitalityDecrease;
|
||||
affliction.CalculateDamagePerSecond(vitalityDecrease);
|
||||
}
|
||||
@@ -627,6 +641,7 @@ namespace Barotrauma
|
||||
private void Kill()
|
||||
{
|
||||
if (Unkillable) { return; }
|
||||
|
||||
var causeOfDeath = GetCauseOfDeath();
|
||||
Character.Kill(causeOfDeath.First, causeOfDeath.Second);
|
||||
}
|
||||
@@ -697,7 +712,9 @@ namespace Barotrauma
|
||||
foreach (Affliction affliction in activeAfflictions)
|
||||
{
|
||||
msg.WriteRangedInteger(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(affliction.Prefab));
|
||||
msg.Write(affliction.Strength);
|
||||
msg.WriteRangedSingle(
|
||||
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
|
||||
0.0f, affliction.Prefab.MaxStrength, 8);
|
||||
}
|
||||
|
||||
List<Pair<LimbHealth, Affliction>> limbAfflictions = new List<Pair<LimbHealth, Affliction>>();
|
||||
@@ -715,7 +732,9 @@ namespace Barotrauma
|
||||
{
|
||||
msg.WriteRangedInteger(0, limbHealths.Count - 1, limbHealths.IndexOf(limbAffliction.First));
|
||||
msg.WriteRangedInteger(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(limbAffliction.Second.Prefab));
|
||||
msg.Write(limbAffliction.Second.Strength);
|
||||
msg.WriteRangedSingle(
|
||||
MathHelper.Clamp(limbAffliction.Second.Strength, 0.0f, limbAffliction.Second.Prefab.MaxStrength),
|
||||
0.0f, limbAffliction.Second.Prefab.MaxStrength, 8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class DamageModifier
|
||||
partial class DamageModifier
|
||||
{
|
||||
[Serialize(1.0f, false)]
|
||||
public float DamageMultiplier
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0.0,360", false)]
|
||||
public Vector2 ArmorSector
|
||||
{
|
||||
@@ -45,17 +45,6 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if CLIENT
|
||||
[Serialize("", false)]
|
||||
public string DamageSound
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
#endif
|
||||
|
||||
public DamageModifier(XElement element, string parentDebugName)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
@@ -96,6 +96,12 @@ namespace Barotrauma
|
||||
{
|
||||
skill.Level += increase;
|
||||
}
|
||||
else
|
||||
{
|
||||
skills.Add(
|
||||
skillIdentifier,
|
||||
new Skill(skillIdentifier, increase));
|
||||
}
|
||||
}
|
||||
|
||||
public void GiveJobItems(Character character, WayPoint spawnPoint = null)
|
||||
@@ -134,11 +140,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Item item = new Item(itemPrefab, character.Position, null);
|
||||
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Entity.Spawner != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (itemElement.GetAttributeBool("equip", false))
|
||||
{
|
||||
|
||||
@@ -41,6 +41,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool OnlyJobSpecificDialog
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//the number of these characters in the crew the player starts with in the single player campaign
|
||||
[Serialize(0, false)]
|
||||
public int InitialCount
|
||||
@@ -101,12 +108,8 @@ namespace Barotrauma
|
||||
public JobPrefab(XElement element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
string translatedName = TextManager.Get("JobName." + Identifier, true);
|
||||
if (!string.IsNullOrEmpty(translatedName)) Name = translatedName;
|
||||
|
||||
string translatedDescription = TextManager.Get("JobDescription." + Identifier, true);
|
||||
if (!string.IsNullOrEmpty(translatedDescription)) Description = translatedDescription;
|
||||
Name = TextManager.Get("JobName." + Identifier);
|
||||
Description = TextManager.Get("JobDescription." + Identifier);
|
||||
|
||||
ItemNames = new List<string>();
|
||||
|
||||
|
||||
@@ -5,21 +5,9 @@ namespace Barotrauma
|
||||
{
|
||||
class SkillPrefab
|
||||
{
|
||||
private string description;
|
||||
|
||||
private Vector2 levelRange;
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
public Vector2 LevelRange
|
||||
{
|
||||
get { return levelRange; }
|
||||
}
|
||||
public Vector2 LevelRange { get; private set; }
|
||||
|
||||
public SkillPrefab(XElement element)
|
||||
{
|
||||
@@ -28,12 +16,12 @@ namespace Barotrauma
|
||||
var levelString = element.GetAttributeString("level", "");
|
||||
if (levelString.Contains(","))
|
||||
{
|
||||
levelRange = XMLExtensions.ParseVector2(levelString, false);
|
||||
LevelRange = XMLExtensions.ParseVector2(levelString, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture);
|
||||
levelRange = new Vector2(skillLevel, skillLevel);
|
||||
LevelRange = new Vector2(skillLevel, skillLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,17 +390,17 @@ namespace Barotrauma
|
||||
pullJoint.LocalAnchorA = new Vector2(-pullJoint.LocalAnchorA.X, pullJoint.LocalAnchorA.Y);
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Vector2 position, float damage, float bleedingDamage, float burnDamage, bool playSound)
|
||||
public AttackResult AddDamage(Vector2 simPosition, float damage, float bleedingDamage, float burnDamage, bool playSound)
|
||||
{
|
||||
List<Affliction> afflictions = new List<Affliction>();
|
||||
if (damage > 0.0f) afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage));
|
||||
if (bleedingDamage > 0.0f) afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage));
|
||||
if (burnDamage > 0.0f) afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamage));
|
||||
|
||||
return AddDamage(position, afflictions, playSound);
|
||||
return AddDamage(simPosition, afflictions, playSound);
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Vector2 position, List<Affliction> afflictions, bool playSound)
|
||||
public AttackResult AddDamage(Vector2 simPosition, List<Affliction> afflictions, bool playSound)
|
||||
{
|
||||
List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
||||
//create a copy of the original affliction list to prevent modifying the afflictions of an Attack/StatusEffect etc
|
||||
@@ -410,7 +410,7 @@ namespace Barotrauma
|
||||
foreach (DamageModifier damageModifier in damageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(afflictions[i])) continue;
|
||||
if (SectorHit(damageModifier.ArmorSector, position))
|
||||
if (SectorHit(damageModifier.ArmorSector, simPosition))
|
||||
{
|
||||
afflictions[i] = afflictions[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
appliedDamageModifiers.Add(damageModifier);
|
||||
@@ -422,7 +422,7 @@ namespace Barotrauma
|
||||
foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(afflictions[i])) continue;
|
||||
if (SectorHit(damageModifier.ArmorSector, position))
|
||||
if (SectorHit(damageModifier.ArmorSector, simPosition))
|
||||
{
|
||||
afflictions[i] = afflictions[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
appliedDamageModifiers.Add(damageModifier);
|
||||
@@ -431,39 +431,51 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AddDamageProjSpecific(position, afflictions, playSound, appliedDamageModifiers);
|
||||
AddDamageProjSpecific(simPosition, afflictions, playSound, appliedDamageModifiers);
|
||||
|
||||
return new AttackResult(afflictions, this, appliedDamageModifiers);
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(Vector2 position, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers);
|
||||
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers);
|
||||
|
||||
public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
|
||||
{
|
||||
if (armorSector == Vector2.Zero) return false;
|
||||
|
||||
float rot = body.Rotation;
|
||||
if (Dir == -1) rot -= MathHelper.Pi;
|
||||
if (armorSector == Vector2.Zero) { return false; }
|
||||
|
||||
Vector2 armorLimits = new Vector2(rot - armorSector.X * Dir, rot - armorSector.Y * Dir);
|
||||
// Can't get this to work properly.
|
||||
//float rot = body.Rotation;
|
||||
//if (Dir == -1) { rot -= MathHelper.Pi; }
|
||||
//Vector2 armorLimits = new Vector2(rot - armorSector.X * Dir, rot - armorSector.Y * Dir);
|
||||
//float mid = (armorLimits.X + armorLimits.Y) / 2;
|
||||
//float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid);
|
||||
//return (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2);
|
||||
|
||||
float mid = (armorLimits.X + armorLimits.Y) / 2.0f;
|
||||
float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid);
|
||||
// Alternative implementation
|
||||
float offset = GetArmorSectorRotationOffset(armorSector, body.Rotation);
|
||||
Vector2 forward = Vector2.Transform(-Vector2.UnitY, Matrix.CreateRotationZ(offset));
|
||||
float hitAngle = VectorExtensions.Angle(forward, simPosition - SimPosition);
|
||||
float sectorSize = MathHelper.ToDegrees(GetArmorSectorSize(armorSector));
|
||||
return hitAngle < sectorSize / 2;
|
||||
}
|
||||
|
||||
return (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2.0f);
|
||||
protected float GetArmorSectorRotationOffset(Vector2 armorSector, float bodyRotation)
|
||||
{
|
||||
float midAngle = MathUtils.GetMidAngle(armorSector.X, armorSector.Y);
|
||||
float spritesheetOrientation = MathHelper.ToRadians(limbParams.Ragdoll.SpritesheetOrientation);
|
||||
return bodyRotation + (midAngle + spritesheetOrientation) * Dir;
|
||||
}
|
||||
|
||||
protected float GetArmorSectorSize(Vector2 armorSector)
|
||||
{
|
||||
float min = Math.Min(armorSector.X, armorSector.Y);
|
||||
float max = Math.Max(armorSector.X, armorSector.Y);
|
||||
return max - min;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
if (LinearVelocity.X > 500.0f)
|
||||
{
|
||||
//DebugConsole.ThrowError("CHARACTER EXPLODED");
|
||||
body.ResetDynamics();
|
||||
body.SetTransform(character.SimPosition, 0.0f);
|
||||
}
|
||||
|
||||
if (inWater)
|
||||
{
|
||||
body.ApplyWaterForces();
|
||||
@@ -512,9 +524,10 @@ namespace Barotrauma
|
||||
SimPosition, attackPosition,
|
||||
ignoredBodies, Physics.CollisionWall);
|
||||
|
||||
if (damageTarget is Item && structureBody?.UserData is Item)
|
||||
if (damageTarget is Item)
|
||||
{
|
||||
// If the attack is aimed to an item and hits an item, it's successful
|
||||
// If the attack is aimed to an item and hits an item, it's successful.
|
||||
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
|
||||
wasHit = true;
|
||||
}
|
||||
else if (damageTarget is Structure && structureBody?.UserData is Structure)
|
||||
|
||||
Reference in New Issue
Block a user