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)
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Barotrauma
|
||||
Decals,
|
||||
NPCConversations,
|
||||
Afflictions,
|
||||
Buffs,
|
||||
Tutorials,
|
||||
UIStyle
|
||||
}
|
||||
@@ -99,6 +100,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public string SteamWorkshopUrl;
|
||||
public DateTime? InstallTime;
|
||||
|
||||
public bool HideInWorkshopMenu
|
||||
{
|
||||
@@ -160,6 +162,10 @@ namespace Barotrauma
|
||||
CorePackage = doc.Root.GetAttributeBool("corepackage", false);
|
||||
SteamWorkshopUrl = doc.Root.GetAttributeString("steamworkshopurl", "");
|
||||
GameVersion = new Version(doc.Root.GetAttributeString("gameversion", "0.0.0.0"));
|
||||
if (doc.Root.Attribute("installtime") != null)
|
||||
{
|
||||
InstallTime = ToolBox.Epoch.ToDateTime(doc.Root.GetAttributeUInt("installtime", 0));
|
||||
}
|
||||
|
||||
List<string> errorMsgs = new List<string>();
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
@@ -268,6 +274,11 @@ namespace Barotrauma
|
||||
doc.Root.Add(new XAttribute("steamworkshopurl", SteamWorkshopUrl));
|
||||
}
|
||||
|
||||
if (InstallTime != null)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("installtime", ToolBox.Epoch.FromDateTime(InstallTime.Value)));
|
||||
}
|
||||
|
||||
foreach (ContentFile file in Files)
|
||||
{
|
||||
doc.Root.Add(new XElement(file.Type.ToString(), new XAttribute("file", file.Path)));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -16,7 +17,9 @@ namespace Barotrauma
|
||||
|
||||
public Exception Exception;
|
||||
|
||||
public CoroutineHandle(IEnumerator<object> coroutine, string name = "")
|
||||
public Thread Thread;
|
||||
|
||||
public CoroutineHandle(IEnumerator<object> coroutine, string name = "", bool useSeparateThread = false)
|
||||
{
|
||||
Coroutine = coroutine;
|
||||
Name = string.IsNullOrWhiteSpace(name) ? coroutine.ToString() : name;
|
||||
@@ -32,10 +35,24 @@ namespace Barotrauma
|
||||
|
||||
public static float UnscaledDeltaTime, DeltaTime;
|
||||
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<object> func, string name = "")
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<object> func, string name = "", bool useSeparateThread = false)
|
||||
{
|
||||
var handle = new CoroutineHandle(func.GetEnumerator(), name);
|
||||
Coroutines.Add(handle);
|
||||
lock (Coroutines)
|
||||
{
|
||||
Coroutines.Add(handle);
|
||||
}
|
||||
|
||||
handle.Thread = null;
|
||||
if (useSeparateThread)
|
||||
{
|
||||
handle.Thread = new Thread(() => { ExecuteCoroutineThread(handle); })
|
||||
{
|
||||
Name = "Coroutine Thread (" + handle.Name + ")",
|
||||
IsBackground = true
|
||||
};
|
||||
handle.Thread.Start();
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -79,33 +96,82 @@ namespace Barotrauma
|
||||
{
|
||||
Coroutines.RemoveAll(c => c == handle);
|
||||
}
|
||||
private static bool IsDone(CoroutineHandle handle)
|
||||
|
||||
public static void ExecuteCoroutineThread(CoroutineHandle handle)
|
||||
{
|
||||
try
|
||||
while (true)
|
||||
{
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
if (wfs != null)
|
||||
{
|
||||
if (!wfs.CheckFinished(UnscaledDeltaTime)) return false;
|
||||
Thread.Sleep((int)(wfs.TotalTime * 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ((CoroutineStatus)handle.Coroutine.Current)
|
||||
{
|
||||
case CoroutineStatus.Success:
|
||||
return true;
|
||||
return;
|
||||
|
||||
case CoroutineStatus.Failure:
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle.Coroutine.MoveNext();
|
||||
return false;
|
||||
Thread.Yield();
|
||||
if (!handle.Coroutine.MoveNext()) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDone(CoroutineHandle handle)
|
||||
{
|
||||
#if !DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (handle.Thread == null)
|
||||
{
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
if (wfs != null)
|
||||
{
|
||||
if (!wfs.CheckFinished(UnscaledDeltaTime)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ((CoroutineStatus)handle.Coroutine.Current)
|
||||
{
|
||||
case CoroutineStatus.Success:
|
||||
return true;
|
||||
|
||||
case CoroutineStatus.Failure:
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle.Coroutine.MoveNext();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handle.Thread.ThreadState.HasFlag(ThreadState.Stopped))
|
||||
{
|
||||
if ((CoroutineStatus)handle.Coroutine.Current == CoroutineStatus.Failure)
|
||||
{
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#if !DEBUG
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -113,6 +179,7 @@ namespace Barotrauma
|
||||
handle.Exception = e;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Updating just means stepping through all the coroutines
|
||||
public static void Update(float unscaledDeltaTime, float deltaTime)
|
||||
@@ -128,11 +195,14 @@ namespace Barotrauma
|
||||
|
||||
class WaitForSeconds
|
||||
{
|
||||
public readonly float TotalTime;
|
||||
|
||||
float timer;
|
||||
|
||||
public WaitForSeconds(float time)
|
||||
{
|
||||
timer = time;
|
||||
TotalTime = time;
|
||||
}
|
||||
|
||||
public bool CheckFinished(float deltaTime)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,10 +79,12 @@ namespace Barotrauma
|
||||
DebugConsole.NewMessage("Initialized ArtifactEvent (" + item.Name + ")", Color.White);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
|
||||
public void StartRound(Level level)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
|
||||
var suitableSettings = EventManagerSettings.List.FindAll(s =>
|
||||
level.Difficulty >= s.MinLevelDifficulty &&
|
||||
@@ -186,8 +186,8 @@ namespace Barotrauma
|
||||
//clients only calculate the intensity but don't create any events
|
||||
//(the intensity is used for controlling the background music)
|
||||
CalculateCurrentIntensity(deltaTime);
|
||||
|
||||
if (GameMain.Client != null) { return; }
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
||||
|
||||
roundDuration += deltaTime;
|
||||
|
||||
@@ -225,12 +225,14 @@ namespace Barotrauma
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead) continue;
|
||||
#if CLIENT
|
||||
if ((character.AIController is HumanAIController || character.IsRemotePlayer || character == Character.Controlled) &&
|
||||
(GameMain.NetworkMember?.Character == null || GameMain.NetworkMember.Character.TeamID == character.TeamID))
|
||||
(GameMain.Client?.Character == null || GameMain.Client.Character.TeamID == character.TeamID))
|
||||
{
|
||||
avgCrewHealth += character.Vitality / character.MaxVitality * (character.IsUnconscious ? 0.5f : 1.0f);
|
||||
characterCount++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (characterCount > 0)
|
||||
{
|
||||
|
||||
@@ -7,57 +7,44 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CombatMission : Mission
|
||||
partial class CombatMission : Mission
|
||||
{
|
||||
private Submarine[] subs;
|
||||
private List<Character>[] crews;
|
||||
|
||||
private bool initialized = false;
|
||||
private int state = 0;
|
||||
private int winner = -1;
|
||||
|
||||
private string[] descriptions;
|
||||
|
||||
private static string[] teamNames = { "Team A", "Team B" };
|
||||
|
||||
private bool initialized = false;
|
||||
|
||||
public override bool AllowRespawn
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public int Winner
|
||||
{
|
||||
get { return winner; }
|
||||
}
|
||||
|
||||
public override string Description
|
||||
private Character.TeamType Winner
|
||||
{
|
||||
get
|
||||
{
|
||||
if (descriptions == null) return "";
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.Character == null)
|
||||
{
|
||||
//non-team-specific description
|
||||
return descriptions[0];
|
||||
}
|
||||
|
||||
//team specific
|
||||
return descriptions[GameMain.NetworkMember.Character.TeamID];
|
||||
if (GameMain.GameSession?.WinningTeam == null) { return Character.TeamType.None; }
|
||||
return GameMain.GameSession.WinningTeam.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string SuccessMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (winner == -1) return "";
|
||||
if (Winner == Character.TeamType.None) { return ""; }
|
||||
|
||||
var loser = Winner == Character.TeamType.Team1 ?
|
||||
Character.TeamType.Team2 :
|
||||
Character.TeamType.Team1;
|
||||
|
||||
return base.SuccessMessage
|
||||
.Replace("[loser]", teamNames[1 - winner])
|
||||
.Replace("[winner]", teamNames[winner]);
|
||||
.Replace("[loser]", GetTeamName(loser))
|
||||
.Replace("[winner]", GetTeamName(Winner));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,63 +73,27 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetTeamName(int teamID)
|
||||
public static string GetTeamName(Character.TeamType teamID)
|
||||
{
|
||||
//team IDs start from 1, while teamName array starts from 0
|
||||
teamID--;
|
||||
|
||||
if (teamID < 0 || teamID >= teamNames.Length)
|
||||
if (teamID == Character.TeamType.Team1)
|
||||
{
|
||||
return "Team " + teamID;
|
||||
return teamNames.Length > 0 ? teamNames[0] : "Team 1";
|
||||
}
|
||||
else if (teamID == Character.TeamType.Team2)
|
||||
{
|
||||
return teamNames.Length > 1 ? teamNames[1] : "Team 2";
|
||||
}
|
||||
|
||||
return teamNames[teamID];
|
||||
return "Invalid Team";
|
||||
}
|
||||
|
||||
public bool IsInWinningTeam(Character character)
|
||||
{
|
||||
return character != null && winner > -1 && character.TeamID - 1 == winner;
|
||||
return character != null &&
|
||||
Winner != Character.TeamType.None &&
|
||||
Winner == character.TeamID;
|
||||
}
|
||||
|
||||
public override bool AssignTeamIDs(List<Client> clients, out byte hostTeam)
|
||||
{
|
||||
List<Client> randList = new List<Client>(clients);
|
||||
for (int i = 0; i < randList.Count; i++)
|
||||
{
|
||||
Client a = randList[i];
|
||||
int oi = Rand.Range(0, randList.Count - 1);
|
||||
Client b = randList[oi];
|
||||
randList[i] = b;
|
||||
randList[oi] = a;
|
||||
}
|
||||
int halfPlayers = randList.Count / 2;
|
||||
for (int i = 0; i < randList.Count; i++)
|
||||
{
|
||||
if (i < halfPlayers)
|
||||
{
|
||||
randList[i].TeamID = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
randList[i].TeamID = 2;
|
||||
}
|
||||
}
|
||||
if (halfPlayers * 2 == randList.Count)
|
||||
{
|
||||
hostTeam = (byte)Rand.Range(1, 2);
|
||||
}
|
||||
else if (halfPlayers * 2 < randList.Count)
|
||||
{
|
||||
hostTeam = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hostTeam = 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (GameMain.NetworkMember == null)
|
||||
@@ -152,7 +103,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
|
||||
subs[0].TeamID = 1; subs[1].TeamID = 2;
|
||||
subs[0].TeamID = Character.TeamType.Team1; subs[1].TeamID = Character.TeamType.Team2;
|
||||
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
|
||||
subs[1].FlipX();
|
||||
|
||||
@@ -182,84 +133,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
crews[0].Clear();
|
||||
crews[1].Clear();
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.TeamID == 1)
|
||||
{
|
||||
crews[0].Add(character);
|
||||
}
|
||||
else if (character.TeamID == 2)
|
||||
{
|
||||
crews[1].Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
//no characters in one of the teams, the client may not have received all spawn messages yet
|
||||
if (crews[0].Count == 0 || crews[1].Count == 0) return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
bool[] teamDead =
|
||||
{
|
||||
crews[0].All(c => c.IsDead || c.IsUnconscious),
|
||||
crews[1].All(c => c.IsDead || c.IsUnconscious)
|
||||
};
|
||||
|
||||
if (state == 0)
|
||||
{
|
||||
for (int i = 0; i < teamDead.Length; i++)
|
||||
{
|
||||
if (!teamDead[i] && teamDead[1 - i])
|
||||
{
|
||||
//make sure nobody in the other team can be revived because that would be pretty weird
|
||||
crews[1 - i].ForEach(c => { if (!c.IsDead) c.Kill(CauseOfDeathType.Unknown, null); });
|
||||
|
||||
winner = i;
|
||||
|
||||
#if CLIENT
|
||||
ShowMessage(i);
|
||||
#endif
|
||||
state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (winner >= 0)
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.WinningTeam = winner + 1;
|
||||
#endif
|
||||
if (GameMain.Server != null) GameMain.Server.EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
if (teamDead[0] && teamDead[1])
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.WinningTeam = 0;
|
||||
#endif
|
||||
winner = -1;
|
||||
if (GameMain.Server != null) GameMain.Server.EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
|
||||
if (winner > -1)
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
|
||||
if (Winner != Character.TeamType.None)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
|
||||
@@ -73,8 +73,8 @@ namespace Barotrauma
|
||||
|
||||
Prefab = prefab;
|
||||
|
||||
Description = prefab.Description;
|
||||
SuccessMessage = prefab.SuccessMessage;
|
||||
description = prefab.Description;
|
||||
successMessage = prefab.SuccessMessage;
|
||||
FailureMessage = prefab.FailureMessage;
|
||||
Headers = new List<string>(prefab.Headers);
|
||||
Messages = new List<string>(prefab.Messages);
|
||||
@@ -83,9 +83,9 @@ namespace Barotrauma
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (Description != null) Description = Description.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (SuccessMessage != null) SuccessMessage = SuccessMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (FailureMessage != null) FailureMessage = FailureMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (description != null) description = description.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (successMessage != null) successMessage = successMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (failureMessage != null) failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
for (int m = 0; m < Messages.Count; m++)
|
||||
{
|
||||
Messages[m] = Messages[m].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
@@ -103,10 +103,12 @@ namespace Barotrauma
|
||||
if (missionType == MissionType.Random)
|
||||
{
|
||||
allowedMissions.AddRange(MissionPrefab.List);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
allowedMissions.RemoveAll(mission => !GameMain.Server.AllowedRandomMissionTypes.Contains(mission.type));
|
||||
allowedMissions.RemoveAll(mission => !GameMain.Server.ServerSettings.AllowedRandomMissionTypes.Contains(mission.type));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (missionType == MissionType.None)
|
||||
{
|
||||
@@ -141,10 +143,9 @@ namespace Barotrauma
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual bool AssignTeamIDs(List<Networking.Client> clients, out byte hostTeam)
|
||||
public virtual bool AssignTeamIDs(List<Networking.Client> clients)
|
||||
{
|
||||
clients.ForEach(c => c.TeamID = 1);
|
||||
hostTeam = 1;
|
||||
clients.ForEach(c => c.TeamID = Character.TeamType.Team1);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,10 +138,10 @@ namespace Barotrauma
|
||||
foreach (Pair<string, string> allowedLocationType in AllowedLocationTypes)
|
||||
{
|
||||
if (allowedLocationType.First.ToLowerInvariant() == "any" ||
|
||||
allowedLocationType.First.ToLowerInvariant() == from.Type.Name.ToLowerInvariant())
|
||||
allowedLocationType.First.ToLowerInvariant() == from.Type.Identifier.ToLowerInvariant())
|
||||
{
|
||||
if (allowedLocationType.Second.ToLowerInvariant() == "any" ||
|
||||
allowedLocationType.Second.ToLowerInvariant() == to.Type.Name.ToLowerInvariant())
|
||||
allowedLocationType.Second.ToLowerInvariant() == to.Type.Identifier.ToLowerInvariant())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@ namespace Barotrauma
|
||||
{
|
||||
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
|
||||
monster = Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, GameMain.Client != null, true, false);
|
||||
bool isClient = false;
|
||||
#if CLIENT
|
||||
isClient = GameMain.Client != null;
|
||||
#endif
|
||||
monster = Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false);
|
||||
monster.Enabled = false;
|
||||
sonarPosition = spawnPos;
|
||||
}
|
||||
|
||||
@@ -70,11 +70,12 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
List<string> monsterNames = GameMain.NetworkMember.monsterEnabled.Keys.ToList();
|
||||
List<string> monsterNames = GameMain.NetworkMember.ServerSettings.MonsterEnabled.Keys.ToList();
|
||||
string tryKey = monsterNames.Find(s => characterFileName == s.ToLower());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tryKey))
|
||||
{
|
||||
if (!GameMain.NetworkMember.monsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
|
||||
if (!GameMain.NetworkMember.ServerSettings.MonsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +162,7 @@ namespace Barotrauma
|
||||
|
||||
//only found a spawnpos that's very far from the sub, pick one that's closer
|
||||
//and wait for the sub to move further before spawning
|
||||
if (closestDist > 10000.0f * 10000.0f)
|
||||
if (closestDist > 15000.0f * 15000.0f)
|
||||
{
|
||||
foreach (Vector2 position in availablePositions)
|
||||
{
|
||||
@@ -192,8 +193,8 @@ namespace Barotrauma
|
||||
|
||||
private float GetMinDistanceToSub(Submarine submarine)
|
||||
{
|
||||
//12000 units is slightly more than the default range of the sonar
|
||||
return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), 12000.0f);
|
||||
//9000 units is slightly less than the default range of the sonar
|
||||
return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), 9000.0f);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -224,16 +225,23 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
bool isClient = false;
|
||||
#if CLIENT
|
||||
isClient = GameMain.Client != null;
|
||||
#endif
|
||||
|
||||
monsters[i] = Character.Create(
|
||||
characterFile, spawnPos + Rand.Vector(100.0f, Rand.RandSync.Server),
|
||||
i.ToString(), null, GameMain.Client != null, true, true);
|
||||
i.ToString(), null, isClient, true, true);
|
||||
}
|
||||
|
||||
spawnPending = false;
|
||||
}
|
||||
|
||||
Entity targetEntity = Character.Controlled != null ?
|
||||
(Entity)Character.Controlled : Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null) targetEntity = (Entity)Character.Controlled;
|
||||
#endif
|
||||
|
||||
bool monstersDead = true;
|
||||
foreach (Character monster in monsters)
|
||||
|
||||
@@ -141,10 +141,12 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
availableContainers.Add(itemContainer, itemContainer.Capacity);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < pi.Quantity; i++)
|
||||
@@ -152,14 +154,18 @@ namespace Barotrauma
|
||||
if (itemContainer == null)
|
||||
{
|
||||
//no container, place at the waypoint
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
#if SERVER
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
//if the intial container has been removed due to it running out of space, add a new container
|
||||
@@ -169,22 +175,28 @@ namespace Barotrauma
|
||||
Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine);
|
||||
itemContainer = containerItemOverFlow.GetComponent<ItemContainer>();
|
||||
availableContainers.Add(itemContainer, itemContainer.Capacity);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//place in the container
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer.Inventory.TryPutItem(item, null);
|
||||
#if SERVER
|
||||
}
|
||||
#endif
|
||||
|
||||
//reduce the number of available slots in the container
|
||||
//if there is a container
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Barotrauma
|
||||
}
|
||||
activeOrders.RemoveAll(o => o.Second <= 0.0f);
|
||||
|
||||
UpdateConversations(deltaTime);
|
||||
UpdateProjectSpecific(deltaTime);
|
||||
}
|
||||
|
||||
@@ -82,29 +83,14 @@ namespace Barotrauma
|
||||
pendingConversationLines.AddRange(conversationLines);
|
||||
}
|
||||
|
||||
partial void CreateRandomConversation();
|
||||
|
||||
private void UpdateConversations(float deltaTime)
|
||||
{
|
||||
conversationTimer -= deltaTime;
|
||||
if (conversationTimer <= 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
List<Character> availableSpeakers = GameMain.GameSession.CrewManager.GetCharacters().ToList();
|
||||
availableSpeakers.RemoveAll(c => !(c.AIController is HumanAIController) || c.IsDead || c.SpeechImpediment >= 100.0f);
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (client.Character != null) availableSpeakers.Remove(client.Character);
|
||||
}
|
||||
if (GameMain.Server.Character != null) availableSpeakers.Remove(GameMain.Server.Character);
|
||||
}
|
||||
#else
|
||||
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
!c.IsDead &&
|
||||
c.SpeechImpediment <= 100.0f);
|
||||
#endif
|
||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||
CreateRandomConversation();
|
||||
conversationTimer = Rand.Range(ConversationIntervalMin, ConversationIntervalMax);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Barotrauma
|
||||
|
||||
const int InitialMoney = 4500;
|
||||
|
||||
private bool watchmenSpawned;
|
||||
private Character startWatchman, endWatchman;
|
||||
protected bool watchmenSpawned;
|
||||
protected Character startWatchman, endWatchman;
|
||||
|
||||
//key = dialog flag, double = Timing.TotalTime when the line was last said
|
||||
private Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
@@ -76,13 +76,18 @@ namespace Barotrauma
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (GameMain.Client != null || !IsRunning) { return; }
|
||||
|
||||
if (!IsRunning) { return; }
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) { return; }
|
||||
#endif
|
||||
if (!watchmenSpawned)
|
||||
{
|
||||
if (Level.Loaded.StartOutpost != null) { startWatchman = SpawnWatchman(Level.Loaded.StartOutpost); }
|
||||
if (Level.Loaded.EndOutpost != null) { endWatchman = SpawnWatchman(Level.Loaded.EndOutpost); }
|
||||
watchmenSpawned = true;
|
||||
#if SERVER
|
||||
(this as MultiPlayerCampaign).LastUpdateID++;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -133,13 +138,7 @@ namespace Barotrauma
|
||||
CharacterInfo characterInfo = new CharacterInfo(Character.HumanConfigFile, jobPrefab: watchmanJob);
|
||||
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
||||
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
||||
spawnedCharacter.CharacterHealth.UseHealthWindow = false;
|
||||
spawnedCharacter.CharacterHealth.Unkillable = true;
|
||||
spawnedCharacter.CanInventoryBeAccessed = false;
|
||||
spawnedCharacter.CanBeDragged = false;
|
||||
spawnedCharacter.SetCustomInteract(
|
||||
WatchmanInteract,
|
||||
hudText: TextManager.Get("TalkHint").Replace("[key]", GameMain.Config.KeyBind(InputType.Select).ToString()));
|
||||
InitializeWatchman(spawnedCharacter);
|
||||
(spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder(
|
||||
new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false));
|
||||
if (watchmanJob != null)
|
||||
@@ -149,6 +148,18 @@ namespace Barotrauma
|
||||
return spawnedCharacter;
|
||||
}
|
||||
|
||||
protected void InitializeWatchman(Character character)
|
||||
{
|
||||
character.CharacterHealth.UseHealthWindow = false;
|
||||
character.CharacterHealth.Unkillable = true;
|
||||
character.CanInventoryBeAccessed = false;
|
||||
character.CanBeDragged = false;
|
||||
character.TeamID = Character.TeamType.FriendlyNPC;
|
||||
character.SetCustomInteract(
|
||||
WatchmanInteract,
|
||||
hudText: TextManager.Get("TalkHint").Replace("[key]", GameMain.Config.KeyBind(InputType.Select).ToString()));
|
||||
}
|
||||
|
||||
protected abstract void WatchmanInteract(Character watchman, Character interactor);
|
||||
|
||||
public abstract void Save(XElement element);
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Networking;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterCampaignData
|
||||
partial class CharacterCampaignData
|
||||
{
|
||||
public readonly CharacterInfo CharacterInfo;
|
||||
public CharacterInfo CharacterInfo
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
public readonly bool IsHostCharacter;
|
||||
|
||||
public readonly string ClientIP;
|
||||
public readonly ulong SteamID;
|
||||
public string ClientIP
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public ulong SteamID
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private XElement itemData;
|
||||
|
||||
partial void InitProjSpecific(Client client);
|
||||
public CharacterCampaignData(Client client)
|
||||
{
|
||||
Name = client.Name;
|
||||
ClientIP = client.Connection.RemoteEndPoint.Address.ToString();
|
||||
SteamID = client.SteamID;
|
||||
CharacterInfo = client.CharacterInfo;
|
||||
InitProjSpecific(client);
|
||||
|
||||
if (client.Character.Inventory != null)
|
||||
{
|
||||
@@ -35,32 +38,16 @@ namespace Barotrauma
|
||||
client.Character.SaveInventory(client.Character.Inventory, itemData);
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterCampaignData(GameServer server)
|
||||
{
|
||||
Name = server.Character.Name;
|
||||
CharacterInfo = server.Character.Info;
|
||||
IsHostCharacter = true;
|
||||
|
||||
if (server.Character.Inventory != null)
|
||||
{
|
||||
itemData = new XElement("inventory");
|
||||
server.Character.SaveInventory(server.Character.Inventory, itemData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public CharacterCampaignData(XElement element)
|
||||
{
|
||||
Name = element.GetAttributeString("name", "Unnamed");
|
||||
IsHostCharacter = element.GetAttributeBool("host", false);
|
||||
if (!IsHostCharacter)
|
||||
Name = element.GetAttributeString("name", "Unnamed");
|
||||
ClientIP = element.GetAttributeString("ip", "");
|
||||
string steamID = element.GetAttributeString("steamid", "");
|
||||
if (!string.IsNullOrEmpty(steamID))
|
||||
{
|
||||
ClientIP = element.GetAttributeString("ip", "");
|
||||
string steamID = element.GetAttributeString("steamid", "");
|
||||
if (!string.IsNullOrEmpty(steamID))
|
||||
{
|
||||
ulong.TryParse(steamID, out SteamID);
|
||||
}
|
||||
ulong.TryParse(steamID, out ulong parsedID);
|
||||
SteamID = parsedID;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
@@ -78,33 +65,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesClient(Client client)
|
||||
{
|
||||
if (IsHostCharacter) return false;
|
||||
if (SteamID > 0)
|
||||
{
|
||||
return SteamID == client.SteamID;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ClientIP == client.Connection.RemoteEndPoint.Address.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement("CharacterCampaignData",
|
||||
new XAttribute("name", Name));
|
||||
|
||||
if (IsHostCharacter)
|
||||
{
|
||||
element.Add(new XAttribute("host", true));
|
||||
}
|
||||
else
|
||||
{
|
||||
element.Add(new XAttribute("ip", ClientIP));
|
||||
element.Add(new XAttribute("steamid", SteamID));
|
||||
}
|
||||
XElement element = new XElement("CharacterCampaignData",
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("ip", ClientIP),
|
||||
new XAttribute("steamid", SteamID));
|
||||
|
||||
CharacterInfo?.Save(element);
|
||||
|
||||
@@ -114,11 +80,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public void SpawnInventoryItems(CharacterInfo characterInfo, Inventory inventory)
|
||||
{
|
||||
characterInfo.SpawnInventoryItems(inventory, itemData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,11 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true);
|
||||
new GameModePreset("tutorial", typeof(TutorialMode), true);
|
||||
#endif
|
||||
new GameModePreset("devsandbox", typeof(GameMode), true)
|
||||
{
|
||||
Description = "Single player sandbox mode for debugging."
|
||||
};
|
||||
|
||||
#endif
|
||||
new GameModePreset("sandbox", typeof(GameMode), false)
|
||||
{
|
||||
Description = "A game mode with no specific objectives."
|
||||
@@ -87,7 +86,9 @@ namespace Barotrauma
|
||||
+ "when the task is completed or everyone in the crew has died."
|
||||
};
|
||||
|
||||
//new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
#if DEBUG
|
||||
new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,26 @@ namespace Barotrauma
|
||||
private UInt16 lastUpdateID;
|
||||
public UInt16 LastUpdateID
|
||||
{
|
||||
get { if (GameMain.Server != null && lastUpdateID < 1) lastUpdateID++; return lastUpdateID; }
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastUpdateID < 1) lastUpdateID++;
|
||||
#endif
|
||||
return lastUpdateID;
|
||||
}
|
||||
set { lastUpdateID = value; }
|
||||
}
|
||||
|
||||
private UInt16 lastSaveID;
|
||||
public UInt16 LastSaveID
|
||||
{
|
||||
get { if (GameMain.Server != null && lastSaveID < 1) lastSaveID++; return lastSaveID; }
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastSaveID < 1) lastSaveID++;
|
||||
#endif
|
||||
return lastSaveID;
|
||||
}
|
||||
set { lastSaveID = value; }
|
||||
}
|
||||
|
||||
@@ -47,124 +59,34 @@ namespace Barotrauma
|
||||
CampaignID = currentCampaignID;
|
||||
}
|
||||
|
||||
private void SetDelegates()
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CargoManager.OnItemsChanged += () => { LastUpdateID++; };
|
||||
Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; };
|
||||
Map.OnMissionSelected += (loc, mission) => { LastUpdateID++; };
|
||||
}
|
||||
}
|
||||
|
||||
public void DiscardClientCharacterData(Client client)
|
||||
{
|
||||
characterData.RemoveAll(cd => cd.MatchesClient(client));
|
||||
}
|
||||
|
||||
public CharacterCampaignData GetClientCharacterData(Client client)
|
||||
{
|
||||
return characterData.Find(cd => cd.MatchesClient(client));
|
||||
}
|
||||
|
||||
public CharacterCampaignData GetHostCharacterData()
|
||||
{
|
||||
return characterData.Find(cd => cd.IsHostCharacter);
|
||||
}
|
||||
|
||||
public void AssignPlayerCharacterInfos(IEnumerable<Client> connectedClients, bool assignHost)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
if (client.SpectateOnly && GameMain.Server.AllowSpectating) continue;
|
||||
var matchingData = GetClientCharacterData(client);
|
||||
if (matchingData != null) client.CharacterInfo = matchingData.CharacterInfo;
|
||||
}
|
||||
|
||||
if (assignHost)
|
||||
{
|
||||
var hostCharacterData = GetHostCharacterData();
|
||||
if (hostCharacterData?.CharacterInfo != null)
|
||||
{
|
||||
GameMain.Server.CharacterInfo = hostCharacterData.CharacterInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<Client, Job> GetAssignedJobs(IEnumerable<Client> connectedClients)
|
||||
{
|
||||
var assignedJobs = new Dictionary<Client, Job>();
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
var matchingData = GetClientCharacterData(client);
|
||||
if (matchingData != null) assignedJobs.Add(client, matchingData.CharacterInfo.Job);
|
||||
}
|
||||
return assignedJobs;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
lastUpdateID++;
|
||||
if (GameMain.NetworkMember.IsServer) lastUpdateID++;
|
||||
}
|
||||
|
||||
|
||||
protected override void WatchmanInteract(Character watchman, Character interactor)
|
||||
{
|
||||
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
|
||||
(watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition))
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CreateDialog(new List<Character> { watchman }, "WatchmanInteractNoLeavingSub", 5.0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasPermissions = true;
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
var client = GameMain.Server.ConnectedClients.Find(c => c.Character == interactor);
|
||||
hasPermissions = client != null &&
|
||||
(client.HasPermission(ClientPermissions.EndRound) || client.HasPermission(ClientPermissions.ManageCampaign));
|
||||
CreateDialog(new List<Character> { watchman }, hasPermissions ? "WatchmanInteract" : "WatchmanInteractNotAllowed", 1.0f);
|
||||
}
|
||||
#if CLIENT
|
||||
else if (GameMain.Client != null && interactor == Character.Controlled && hasPermissions)
|
||||
{
|
||||
var msgBox = new GUIMessageBox("", TextManager.Get("CampaignEnterOutpostPrompt")
|
||||
.Replace("[locationname]", Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
|
||||
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
|
||||
{
|
||||
GameMain.Client.RequestRoundEnd();
|
||||
return true;
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public override void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.GameSession.EndRound("");
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.EndRound();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if SERVER
|
||||
lastUpdateID++;
|
||||
|
||||
bool success =
|
||||
GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead) ||
|
||||
(GameMain.Server.Character != null && !GameMain.Server.Character.IsDead);
|
||||
bool success =
|
||||
GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||
|
||||
#if CLIENT
|
||||
success = success || (GameMain.Server.Character != null && !GameMain.Server.Character.IsDead);
|
||||
#endif
|
||||
|
||||
/*if (success)
|
||||
{
|
||||
@@ -194,21 +116,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
|
||||
#endif
|
||||
|
||||
if (GameMain.Server.Character != null)
|
||||
{
|
||||
characterData.RemoveAll(cd => cd.IsHostCharacter);
|
||||
if (!GameMain.Server.Character.IsDead)
|
||||
{
|
||||
var hostCharacterData = new CharacterCampaignData(GameMain.Server);
|
||||
characterData.Add(hostCharacterData);
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(hostCharacterData.CharacterInfo);
|
||||
#endif
|
||||
}
|
||||
c.Inventory?.DeleteAllItems();
|
||||
}
|
||||
|
||||
//remove all items that are in someone's inventory
|
||||
@@ -250,8 +160,11 @@ namespace Barotrauma
|
||||
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
partial void SetDelegates();
|
||||
|
||||
public static MultiPlayerCampaign LoadNew(XElement element)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(GameModePreset.List.Find(gm => gm.Identifier == "multiplayercampaign"), null);
|
||||
@@ -310,23 +223,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Server != null)
|
||||
characterData.Clear();
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
|
||||
if (characterDataDoc?.Root == null) return;
|
||||
foreach (XElement subElement in characterDataDoc.Root.Elements())
|
||||
{
|
||||
characterData.Clear();
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
|
||||
if (characterDataDoc?.Root == null) return;
|
||||
foreach (XElement subElement in characterDataDoc.Root.Elements())
|
||||
{
|
||||
characterData.Add(new CharacterCampaignData(subElement));
|
||||
}
|
||||
#if CLIENT
|
||||
var hostCharacterData = GetHostCharacterData();
|
||||
if (hostCharacterData?.CharacterInfo != null)
|
||||
{
|
||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(hostCharacterData.CharacterInfo);
|
||||
}
|
||||
#endif
|
||||
characterData.Add(new CharacterCampaignData(subElement));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,76 +259,5 @@ namespace Barotrauma
|
||||
|
||||
lastSaveID++;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
|
||||
|
||||
msg.Write(CampaignID);
|
||||
msg.Write(lastUpdateID);
|
||||
msg.Write(lastSaveID);
|
||||
msg.Write(map.Seed);
|
||||
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
|
||||
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
|
||||
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
|
||||
|
||||
msg.Write(Money);
|
||||
|
||||
msg.Write((UInt16)CargoManager.PurchasedItems.Count);
|
||||
foreach (PurchasedItem pi in CargoManager.PurchasedItems)
|
||||
{
|
||||
msg.Write((UInt16)MapEntityPrefab.List.IndexOf(pi.ItemPrefab));
|
||||
msg.Write((UInt16)pi.Quantity);
|
||||
}
|
||||
|
||||
var characterData = GetClientCharacterData(c);
|
||||
if (characterData?.CharacterInfo == null)
|
||||
{
|
||||
msg.Write(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(true);
|
||||
characterData.CharacterInfo.ServerWrite(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(NetBuffer msg, Client sender)
|
||||
{
|
||||
UInt16 selectedLocIndex = msg.ReadUInt16();
|
||||
byte selectedMissionIndex = msg.ReadByte();
|
||||
UInt16 purchasedItemCount = msg.ReadUInt16();
|
||||
|
||||
List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
|
||||
for (int i = 0; i < purchasedItemCount; i++)
|
||||
{
|
||||
UInt16 itemPrefabIndex = msg.ReadUInt16();
|
||||
UInt16 itemQuantity = msg.ReadUInt16();
|
||||
purchasedItems.Add(new PurchasedItem(MapEntityPrefab.List[itemPrefabIndex] as ItemPrefab, itemQuantity));
|
||||
}
|
||||
|
||||
if (!sender.HasPermission(ClientPermissions.ManageCampaign))
|
||||
{
|
||||
DebugConsole.ThrowError("Client \"" + sender.Name + "\" does not have a permission to manage the campaign");
|
||||
return;
|
||||
}
|
||||
|
||||
Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
|
||||
if (Map.SelectedConnection != null)
|
||||
{
|
||||
Map.SelectMission(selectedMissionIndex);
|
||||
}
|
||||
|
||||
List<PurchasedItem> currentItems = new List<PurchasedItem>(CargoManager.PurchasedItems);
|
||||
foreach (PurchasedItem pi in currentItems)
|
||||
{
|
||||
CargoManager.SellItem(pi, pi.Quantity);
|
||||
}
|
||||
|
||||
foreach (PurchasedItem pi in purchasedItems)
|
||||
{
|
||||
CargoManager.PurchaseItem(pi.ItemPrefab, pi.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public readonly Character Character;
|
||||
public Character TargetCharacter; //TODO: make a modular objective system (similar to crew missions) that allows for things OTHER than assasinations.
|
||||
|
||||
public Traitor(Character character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
|
||||
public void Greet(GameServer server, string codeWords, string codeResponse)
|
||||
{
|
||||
string greetingMessage = TextManager.Get("TraitorStartMessage").Replace("[targetname]", TargetCharacter.Name);
|
||||
string moreAgentsMessage = TextManager.Get("TraitorMoreAgentsMessage")
|
||||
.Replace("[codewords]", codeWords)
|
||||
.Replace("[coderesponse]", codeResponse);
|
||||
|
||||
if (server.Character != Character)
|
||||
{
|
||||
var greetingChatMsg = ChatMessage.Create(null, greetingMessage, ChatMessageType.Server, null);
|
||||
var moreAgentsChatMsg = ChatMessage.Create(null, moreAgentsMessage, ChatMessageType.Server, null);
|
||||
|
||||
var greetingMsgBox = ChatMessage.Create(null, greetingMessage, ChatMessageType.MessageBox, null);
|
||||
var moreAgentsMsgBox = ChatMessage.Create(null, moreAgentsMessage, ChatMessageType.MessageBox, null);
|
||||
|
||||
Client client = server.ConnectedClients.Find(c => c.Character == Character);
|
||||
GameMain.Server.SendDirectChatMessage(greetingChatMsg, client);
|
||||
GameMain.Server.SendDirectChatMessage(moreAgentsChatMsg, client);
|
||||
GameMain.Server.SendDirectChatMessage(greetingMsgBox, client);
|
||||
GameMain.Server.SendDirectChatMessage(moreAgentsMsgBox, client);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (server.Character == null)
|
||||
{
|
||||
new GUIMessageBox(
|
||||
TextManager.Get("NewTraitor"),
|
||||
TextManager.Get("TraitorStartMessageServer").Replace("[targetname]", TargetCharacter.Name).Replace("[traitorname]", Character.Name));
|
||||
}
|
||||
else if (server.Character == Character)
|
||||
{
|
||||
new GUIMessageBox("", greetingMessage);
|
||||
new GUIMessageBox("", moreAgentsMessage);
|
||||
|
||||
GameMain.NetworkMember.AddChatMessage(greetingMessage, ChatMessageType.Server);
|
||||
GameMain.NetworkMember.AddChatMessage(moreAgentsMessage, ChatMessageType.Server);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
partial class TraitorManager
|
||||
{
|
||||
private static string wordsTxt = Path.Combine("Content", "CodeWords.txt");
|
||||
|
||||
public List<Traitor> TraitorList
|
||||
{
|
||||
get { return traitorList; }
|
||||
}
|
||||
|
||||
private List<Traitor> traitorList = new List<Traitor>();
|
||||
|
||||
public string codeWords, codeResponse;
|
||||
|
||||
public TraitorManager(GameServer server, int traitorCount)
|
||||
{
|
||||
if (traitorCount < 1) //what why how
|
||||
{
|
||||
traitorCount = 1;
|
||||
DebugConsole.ThrowError("Traitor Manager: TraitorCount somehow ended up less than 1, setting it to 1.");
|
||||
}
|
||||
Start(server, traitorCount);
|
||||
}
|
||||
|
||||
private void Start(GameServer server, int traitorCount)
|
||||
{
|
||||
if (server == null) return;
|
||||
|
||||
List<Character> characters = new List<Character>(); //ANYONE can be a target.
|
||||
List<Character> traitorCandidates = new List<Character>(); //Keep this to not re-pick traitors twice
|
||||
foreach (Client client in server.ConnectedClients)
|
||||
{
|
||||
if (client.Character != null)
|
||||
{
|
||||
characters.Add(client.Character);
|
||||
traitorCandidates.Add(client.Character);
|
||||
}
|
||||
}
|
||||
|
||||
if (server.Character != null)
|
||||
{
|
||||
characters.Add(server.Character); //Add host character
|
||||
traitorCandidates.Add(server.Character);
|
||||
}
|
||||
|
||||
if (characters.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
codeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
|
||||
codeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
|
||||
|
||||
while (traitorCount-- > 0)
|
||||
{
|
||||
if (traitorCandidates.Count <= 0) break;
|
||||
|
||||
int traitorIndex = Rand.Int(traitorCandidates.Count);
|
||||
Character traitorCharacter = traitorCandidates[traitorIndex];
|
||||
traitorCandidates.Remove(traitorCharacter);
|
||||
|
||||
//Add them to the list
|
||||
traitorList.Add(new Traitor(traitorCharacter));
|
||||
}
|
||||
|
||||
//Now that traitors have been decided, let's do objectives in post for deciding things like Document Exchange.
|
||||
foreach (Traitor traitor in traitorList)
|
||||
{
|
||||
Character traitorCharacter = traitor.Character;
|
||||
int targetIndex = Rand.Int(characters.Count);
|
||||
while (characters[targetIndex] == traitorCharacter) //Cannot target self
|
||||
{
|
||||
targetIndex = Rand.Int(characters.Count);
|
||||
}
|
||||
|
||||
Character targetCharacter = characters[targetIndex];
|
||||
traitor.TargetCharacter = targetCharacter;
|
||||
traitor.Greet(server, codeWords, codeResponse);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetEndMessage()
|
||||
{
|
||||
if (GameMain.Server == null || traitorList.Count <= 0) return "";
|
||||
|
||||
string endMessage = "";
|
||||
|
||||
foreach (Traitor traitor in traitorList)
|
||||
{
|
||||
Character traitorCharacter = traitor.Character;
|
||||
Character targetCharacter = traitor.TargetCharacter;
|
||||
string messageTag;
|
||||
|
||||
if (targetCharacter.IsDead) //Partial or complete mission success
|
||||
{
|
||||
if (traitorCharacter.IsDead)
|
||||
{
|
||||
messageTag = "TraitorEndMessageSuccessTraitorDead";
|
||||
}
|
||||
else if (traitorCharacter.LockHands)
|
||||
{
|
||||
messageTag = "TraitorEndMessageSuccessTraitorDetained";
|
||||
}
|
||||
else
|
||||
messageTag = "TraitorEndMessageSuccess";
|
||||
}
|
||||
else //Partial or complete failure
|
||||
{
|
||||
if (traitorCharacter.IsDead)
|
||||
{
|
||||
messageTag = "TraitorEndMessageFailureTraitorDead";
|
||||
}
|
||||
else if (traitorCharacter.LockHands)
|
||||
{
|
||||
messageTag = "TraitorEndMessageFailureTraitorDetained";
|
||||
}
|
||||
else
|
||||
{
|
||||
messageTag = "TraitorEndMessageFailure";
|
||||
}
|
||||
}
|
||||
|
||||
endMessage += (TextManager.ReplaceGenderPronouns(TextManager.Get(messageTag), traitorCharacter.Info.Gender) + "\n")
|
||||
.Replace("[traitorname]", traitorCharacter.Name)
|
||||
.Replace("[targetname]", targetCharacter.Name);
|
||||
}
|
||||
|
||||
return endMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,36 +11,20 @@ namespace Barotrauma
|
||||
public enum InfoFrameTab { Crew, Mission, ManagePlayers };
|
||||
|
||||
public readonly EventManager EventManager;
|
||||
|
||||
|
||||
public GameMode GameMode;
|
||||
|
||||
//two locations used as the start and end in the MP mode
|
||||
private Location[] dummyLocations;
|
||||
|
||||
private string savePath;
|
||||
|
||||
private Submarine submarine;
|
||||
|
||||
public CrewManager CrewManager;
|
||||
|
||||
public double RoundStartTime;
|
||||
|
||||
private Mission currentMission;
|
||||
public Mission Mission { get; private set; }
|
||||
|
||||
public Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return currentMission;
|
||||
}
|
||||
}
|
||||
public Character.TeamType? WinningTeam;
|
||||
|
||||
private Level level;
|
||||
|
||||
public Level Level
|
||||
{
|
||||
get { return level; }
|
||||
}
|
||||
public Level Level { get; private set; }
|
||||
|
||||
public Map Map
|
||||
{
|
||||
@@ -78,20 +62,13 @@ namespace Barotrauma
|
||||
|
||||
return dummyLocations[1];
|
||||
}
|
||||
}
|
||||
|
||||
public Submarine Submarine
|
||||
{
|
||||
get { return submarine; }
|
||||
set { submarine = value; }
|
||||
}
|
||||
|
||||
public string SavePath
|
||||
{
|
||||
get { return savePath; }
|
||||
set { savePath = value; }
|
||||
}
|
||||
public Submarine Submarine { get; set; }
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public GameSession(Submarine submarine, string savePath, GameModePreset gameModePreset, MissionType missionType = MissionType.None)
|
||||
: this(submarine, savePath)
|
||||
@@ -109,29 +86,23 @@ namespace Barotrauma
|
||||
|
||||
private GameSession(Submarine submarine, string savePath)
|
||||
{
|
||||
InitProjSpecific();
|
||||
Submarine.MainSub = submarine;
|
||||
this.submarine = submarine;
|
||||
this.Submarine = submarine;
|
||||
GameMain.GameSession = this;
|
||||
EventManager = new EventManager(this);
|
||||
this.savePath = savePath;
|
||||
|
||||
#if CLIENT
|
||||
int buttonHeight = (int)(HUDLayoutSettings.ButtonAreaTop.Height * 0.6f);
|
||||
infoButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(HUDLayoutSettings.ButtonAreaTop.X, HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonHeight / 2, 100, buttonHeight), GUICanvas.Instance),
|
||||
TextManager.Get("InfoButton"), textAlignment: Alignment.Center);
|
||||
infoButton.OnClicked = ToggleInfoFrame;
|
||||
#endif
|
||||
this.SavePath = savePath;
|
||||
}
|
||||
|
||||
|
||||
public GameSession(Submarine selectedSub, string saveFile, XDocument doc)
|
||||
: this(selectedSub, saveFile)
|
||||
{
|
||||
Submarine.MainSub = submarine;
|
||||
Submarine.MainSub = Submarine;
|
||||
|
||||
GameMain.GameSession = this;
|
||||
selectedSub.Name = doc.Root.GetAttributeString("submarine", selectedSub.Name);
|
||||
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -171,11 +142,11 @@ namespace Barotrauma
|
||||
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void LoadPrevious()
|
||||
{
|
||||
Submarine.Unload();
|
||||
SaveUtil.LoadGame(savePath);
|
||||
SaveUtil.LoadGame(SavePath);
|
||||
}
|
||||
|
||||
public void StartRound(string levelSeed, float? difficulty = null, bool loadSecondSub = false)
|
||||
@@ -188,19 +159,19 @@ namespace Barotrauma
|
||||
public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false)
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = GameMain.NetworkMember == null || GameMain.NetworkMember.CharacterInfo != null;
|
||||
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
|
||||
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode;
|
||||
#endif
|
||||
this.level = level;
|
||||
this.Level = level;
|
||||
|
||||
if (submarine == null)
|
||||
if (Submarine == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine not selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadSub || Submarine.MainSub != submarine) submarine.Load(true);
|
||||
Submarine.MainSub = submarine;
|
||||
if (reloadSub || Submarine.MainSub != Submarine) Submarine.Load(true);
|
||||
Submarine.MainSub = Submarine;
|
||||
if (loadSecondSub)
|
||||
{
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
@@ -221,14 +192,14 @@ namespace Barotrauma
|
||||
{
|
||||
//start by placing the sub below the outpost
|
||||
Rectangle outpostBorders = Level.Loaded.StartOutpost.GetDockedBorders();
|
||||
Rectangle subBorders = submarine.GetDockedBorders();
|
||||
Rectangle subBorders = Submarine.GetDockedBorders();
|
||||
|
||||
Vector2 startOutpostSize = Vector2.Zero;
|
||||
if (Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
startOutpostSize = Level.Loaded.StartOutpost.Borders.Size.ToVector2();
|
||||
}
|
||||
submarine.SetPosition(
|
||||
Submarine.SetPosition(
|
||||
Level.Loaded.StartOutpost.WorldPosition -
|
||||
new Vector2(0.0f, outpostBorders.Height / 2 + subBorders.Height / 2));
|
||||
|
||||
@@ -243,10 +214,10 @@ namespace Barotrauma
|
||||
outPostPort = port;
|
||||
continue;
|
||||
}
|
||||
if (port.Item.Submarine != submarine) { continue; }
|
||||
if (port.Item.Submarine != Submarine) { continue; }
|
||||
|
||||
//the submarine port has to be at the top of the sub
|
||||
if (port.Item.WorldPosition.Y < submarine.WorldPosition.Y) { continue; }
|
||||
if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition);
|
||||
if (myPort == null || dist < closestDistance)
|
||||
@@ -258,21 +229,21 @@ namespace Barotrauma
|
||||
|
||||
if (myPort != null && outPostPort != null)
|
||||
{
|
||||
Vector2 portDiff = myPort.Item.WorldPosition - submarine.WorldPosition;
|
||||
submarine.SetPosition((outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance);
|
||||
Vector2 portDiff = myPort.Item.WorldPosition - Submarine.WorldPosition;
|
||||
Submarine.SetPosition((outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance);
|
||||
myPort.Dock(outPostPort);
|
||||
myPort.Lock(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
submarine.SetPosition(submarine.FindSpawnPos(level.StartPosition));
|
||||
Submarine.SetPosition(Submarine.FindSpawnPos(level.StartPosition));
|
||||
}
|
||||
}
|
||||
|
||||
Entity.Spawner = new EntitySpawner();
|
||||
|
||||
if (GameMode.Mission != null) currentMission = GameMode.Mission;
|
||||
if (GameMode.Mission != null) Mission = GameMode.Mission;
|
||||
if (GameMode != null) GameMode.Start();
|
||||
if (GameMode.Mission != null) Mission.Start(Level.Loaded);
|
||||
|
||||
@@ -282,34 +253,36 @@ namespace Barotrauma
|
||||
if (GameMode != null)
|
||||
{
|
||||
GameMode.MsgBox();
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.Server != null)
|
||||
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
mpCampaign.CargoManager.CreateItems();
|
||||
}
|
||||
}
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Submarine:" + submarine.Name);
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Name);
|
||||
GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(level.Seed));
|
||||
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start,
|
||||
GameMode.Preset.Identifier, (Mission == null ? "None" : Mission.GetType().ToString()));
|
||||
|
||||
|
||||
|
||||
|
||||
#if CLIENT
|
||||
if (GameMode is SinglePlayerCampaign) SteamAchievementManager.OnBiomeDiscovered(level.Biome);
|
||||
if (GameMode is SinglePlayerCampaign) SteamAchievementManager.OnBiomeDiscovered(level.Biome);
|
||||
roundSummary = new RoundSummary(this);
|
||||
|
||||
GameMain.GameScreen.ColorFade(Color.Black, Color.TransparentBlack, 5.0f);
|
||||
|
||||
if (!(GameMode is TutorialMode))
|
||||
{
|
||||
GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false);
|
||||
GUI.AddMessage(level.Biome.Name, Color.Lerp(Color.CadetBlue, Color.DarkRed, level.Difficulty / 100.0f), 5.0f, playSound: false);
|
||||
GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false);
|
||||
GUI.AddMessage(level.Biome.Name, Color.Lerp(Color.CadetBlue, Color.DarkRed, level.Difficulty / 100.0f), 5.0f, playSound: false);
|
||||
GUI.AddMessage(TextManager.Get("Destination") + ": " + EndLocation.Name, Color.CadetBlue, playSound: false);
|
||||
GUI.AddMessage(TextManager.Get("Mission") + ": " + (Mission == null ? TextManager.Get("None") : Mission.Name), Color.CadetBlue, playSound: false);
|
||||
}
|
||||
#endif
|
||||
|
||||
RoundStartTime = Timing.TotalTime;
|
||||
GameMain.ResetFrameTime();
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
@@ -328,8 +301,8 @@ namespace Barotrauma
|
||||
if (Mission != null) Mission.End();
|
||||
GameAnalyticsManager.AddProgressionEvent(
|
||||
(Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail,
|
||||
GameMode.Preset.Identifier,
|
||||
(Mission == null ? "None" : Mission.GetType().ToString()));
|
||||
GameMode.Preset.Identifier,
|
||||
(Mission == null ? "None" : Mission.GetType().ToString()));
|
||||
|
||||
#if CLIENT
|
||||
if (roundSummary != null)
|
||||
@@ -347,11 +320,11 @@ namespace Barotrauma
|
||||
EventManager.EndRound();
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
|
||||
currentMission = null;
|
||||
Mission = null;
|
||||
|
||||
StatusEffect.StopAll();
|
||||
}
|
||||
|
||||
|
||||
public void KillCharacter(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -378,7 +351,7 @@ namespace Barotrauma
|
||||
|
||||
var now = DateTime.Now;
|
||||
doc.Root.Add(new XAttribute("savetime", now.ToShortTimeString() + ", " + now.ToShortDateString()));
|
||||
doc.Root.Add(new XAttribute("submarine", submarine == null ? "" : submarine.Name));
|
||||
doc.Root.Add(new XAttribute("submarine", Submarine == null ? "" : Submarine.Name));
|
||||
doc.Root.Add(new XAttribute("mapseed", Map.Seed));
|
||||
|
||||
((CampaignMode)GameMode).Save(doc.Root);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HireManager
|
||||
{
|
||||
private List<CharacterInfo> availableCharacters;
|
||||
public IEnumerable<CharacterInfo> AvailableCharacters
|
||||
{
|
||||
get { return availableCharacters; }
|
||||
}
|
||||
|
||||
public const int MaxAvailableCharacters = 10;
|
||||
|
||||
public HireManager()
|
||||
{
|
||||
availableCharacters = new List<CharacterInfo>();
|
||||
}
|
||||
|
||||
public void RemoveCharacter(CharacterInfo character)
|
||||
{
|
||||
availableCharacters.Remove(character);
|
||||
}
|
||||
|
||||
public void GenerateCharacters(Location location, int amount)
|
||||
{
|
||||
availableCharacters.ForEach(c => c.Remove());
|
||||
availableCharacters.Clear();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
JobPrefab job = location.Type.GetRandomHireable();
|
||||
if (job == null) { return; }
|
||||
|
||||
availableCharacters.Add(new CharacterInfo(Character.HumanConfigFile, "", job));
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
availableCharacters.ForEach(c => c.Remove());
|
||||
availableCharacters.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,9 @@ namespace Barotrauma
|
||||
|
||||
public partial class GameSettings
|
||||
{
|
||||
const string FilePath = "config.xml";
|
||||
const string savePath = "config.xml";
|
||||
const string playerSavePath = "config_player.xml";
|
||||
const string vanillaContentPackagePath = "Data/ContentPackages/Vanilla";
|
||||
|
||||
public int GraphicsWidth { get; set; }
|
||||
public int GraphicsHeight { get; set; }
|
||||
@@ -37,10 +39,26 @@ namespace Barotrauma
|
||||
|
||||
public int ParticleLimit { get; set; }
|
||||
|
||||
public int ParticleLimit { get; set; }
|
||||
|
||||
public float LightMapScale { get; set; }
|
||||
public bool SpecularityEnabled { get; set; }
|
||||
public bool ChromaticAberrationEnabled { get; set; }
|
||||
|
||||
public bool MuteOnFocusLost { get; set; }
|
||||
|
||||
public enum VoiceMode
|
||||
{
|
||||
Disabled,
|
||||
PushToTalk,
|
||||
Activity
|
||||
};
|
||||
|
||||
public VoiceMode VoiceSetting { get; set; }
|
||||
public string VoiceCaptureDevice { get; set; }
|
||||
|
||||
public float NoiseGateThreshold { get; set; } = -45;
|
||||
|
||||
private KeyOrMouse[] keyMapping;
|
||||
|
||||
private WindowMode windowMode;
|
||||
@@ -90,10 +108,23 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
public bool AutoUpdateWorkshopItems;
|
||||
|
||||
public WindowMode WindowMode
|
||||
{
|
||||
get { return windowMode; }
|
||||
set { windowMode = value; }
|
||||
set
|
||||
{
|
||||
#if (OSX)
|
||||
// Fullscreen doesn't work on macOS, so just force any usage of it to borderless windowed.
|
||||
if (value == WindowMode.Fullscreen)
|
||||
{
|
||||
windowMode = WindowMode.BorderlessWindowed;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
windowMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> JobPreferences
|
||||
@@ -102,7 +133,7 @@ namespace Barotrauma
|
||||
set { jobPreferences = value; }
|
||||
}
|
||||
|
||||
public int CharacterHeadIndex { get; set; }
|
||||
public int CharacterHeadIndex { get; set; } = 1;
|
||||
public int CharacterHairIndex { get; set; }
|
||||
public int CharacterBeardIndex { get; set; }
|
||||
public int CharacterMoustacheIndex { get; set; }
|
||||
@@ -133,13 +164,13 @@ namespace Barotrauma
|
||||
{
|
||||
//applyButton.Selected = unsavedSettings;
|
||||
applyButton.Enabled = unsavedSettings;
|
||||
applyButton.Text = unsavedSettings ? "Apply*" : "Apply";
|
||||
applyButton.Text = TextManager.Get(unsavedSettings ? "ApplySettingsButtonUnsavedChanges" : "ApplySettingsButton");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private float soundVolume, musicVolume;
|
||||
private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f;
|
||||
|
||||
public float SoundVolume
|
||||
{
|
||||
@@ -170,6 +201,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float VoiceChatVolume
|
||||
{
|
||||
get { return voiceChatVolume; }
|
||||
set
|
||||
{
|
||||
voiceChatVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public string Language
|
||||
{
|
||||
get { return TextManager.Language; }
|
||||
@@ -178,6 +221,8 @@ namespace Barotrauma
|
||||
|
||||
public HashSet<ContentPackage> SelectedContentPackages { get; set; }
|
||||
|
||||
private HashSet<string> selectedContentPackagePaths = new HashSet<string>();
|
||||
|
||||
public string MasterServerUrl { get; set; }
|
||||
public bool AutoCheckUpdates { get; set; }
|
||||
public bool WasGameUpdated { get; set; }
|
||||
@@ -221,24 +266,34 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
sendUserStatistics = value;
|
||||
GameMain.Config.Save();
|
||||
GameMain.Config.SaveNewPlayerConfig();
|
||||
}
|
||||
}
|
||||
public static bool ShowUserStatisticsPrompt { get; set; }
|
||||
|
||||
public GameSettings(string filePath)
|
||||
public GameSettings()
|
||||
{
|
||||
SelectedContentPackages = new HashSet<ContentPackage>();
|
||||
|
||||
ContentPackage.LoadAll(ContentPackage.Folder);
|
||||
CompletedTutorialNames = new List<string>();
|
||||
Load(filePath);
|
||||
|
||||
LoadDefaultConfig();
|
||||
|
||||
if (WasGameUpdated)
|
||||
{
|
||||
UpdaterUtil.CleanOldFiles();
|
||||
WasGameUpdated = false;
|
||||
SaveNewDefaultConfig();
|
||||
}
|
||||
|
||||
LoadPlayerConfig();
|
||||
}
|
||||
|
||||
public void Load(string filePath)
|
||||
#region Load DefaultConfig
|
||||
public void LoadDefaultConfig()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(savePath);
|
||||
|
||||
Language = doc.Root.GetAttributeString("language", "English");
|
||||
|
||||
MasterServerUrl = doc.Root.GetAttributeString("masterserverurl", "");
|
||||
@@ -248,14 +303,11 @@ namespace Barotrauma
|
||||
|
||||
VerboseLogging = doc.Root.GetAttributeBool("verboselogging", false);
|
||||
SaveDebugConsoleLogs = doc.Root.GetAttributeBool("savedebugconsolelogs", false);
|
||||
if (doc.Root.Attribute("senduserstatistics") == null)
|
||||
{
|
||||
ShowUserStatisticsPrompt = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", true);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
UseSteam = doc.Root.GetAttributeBool("usesteam", true);
|
||||
#endif
|
||||
QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsub", "");
|
||||
|
||||
#if DEBUG
|
||||
UseSteam = doc.Root.GetAttributeBool("usesteam", true);
|
||||
@@ -280,8 +332,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
XElement graphicsMode = doc.Root.Element("graphicsmode");
|
||||
GraphicsWidth = graphicsMode.GetAttributeInt("width", 0);
|
||||
GraphicsHeight = graphicsMode.GetAttributeInt("height", 0);
|
||||
GraphicsWidth = 0;
|
||||
GraphicsHeight = 0;
|
||||
VSyncEnabled = graphicsMode.GetAttributeBool("vsync", true);
|
||||
|
||||
XElement graphicsSettings = doc.Root.Element("graphicssettings");
|
||||
@@ -305,21 +357,24 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
//FullScreenEnabled = ToolBox.GetAttributeBool(graphicsMode, "fullscreen", true);
|
||||
|
||||
var windowModeStr = graphicsMode.GetAttributeString("displaymode", "Fullscreen");
|
||||
if (!Enum.TryParse<WindowMode>(windowModeStr, out windowMode))
|
||||
if (!Enum.TryParse(windowModeStr, out WindowMode wm))
|
||||
{
|
||||
windowMode = WindowMode.Fullscreen;
|
||||
wm = WindowMode.Fullscreen;
|
||||
}
|
||||
|
||||
SoundVolume = doc.Root.GetAttributeFloat("soundvolume", 1.0f);
|
||||
MusicVolume = doc.Root.GetAttributeFloat("musicvolume", 0.3f);
|
||||
|
||||
WindowMode = wm;
|
||||
|
||||
useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", true);
|
||||
requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", true);
|
||||
AutoUpdateWorkshopItems = doc.Root.GetAttributeBool("autoupdateworkshopitems", true);
|
||||
|
||||
#if DEBUG
|
||||
EnableSplashScreen = false;
|
||||
#else
|
||||
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", true);
|
||||
#endif
|
||||
|
||||
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
|
||||
|
||||
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
|
||||
|
||||
@@ -330,8 +385,9 @@ namespace Barotrauma
|
||||
keyMapping[(int)InputType.Right] = new KeyOrMouse(Keys.D);
|
||||
keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift);
|
||||
|
||||
keyMapping[(int)InputType.Chat] = new KeyOrMouse(Keys.Tab);
|
||||
keyMapping[(int)InputType.RadioChat] = new KeyOrMouse(Keys.OemPipe);
|
||||
keyMapping[(int)InputType.InfoTab] = new KeyOrMouse(Keys.Tab);
|
||||
keyMapping[(int)InputType.Chat] = new KeyOrMouse(Keys.T);
|
||||
keyMapping[(int)InputType.RadioChat] = new KeyOrMouse(Keys.Y);
|
||||
keyMapping[(int)InputType.CrewOrders] = new KeyOrMouse(Keys.C);
|
||||
|
||||
keyMapping[(int)InputType.Select] = new KeyOrMouse(Keys.E);
|
||||
@@ -339,6 +395,8 @@ namespace Barotrauma
|
||||
keyMapping[(int)InputType.SelectNextCharacter] = new KeyOrMouse(Keys.Tab);
|
||||
keyMapping[(int)InputType.SelectPreviousCharacter] = new KeyOrMouse(Keys.Q);
|
||||
|
||||
keyMapping[(int)InputType.Voice] = new KeyOrMouse(Keys.V);
|
||||
|
||||
keyMapping[(int)InputType.Use] = new KeyOrMouse(0);
|
||||
keyMapping[(int)InputType.Aim] = new KeyOrMouse(1);
|
||||
|
||||
@@ -376,9 +434,11 @@ namespace Barotrauma
|
||||
break;
|
||||
case "player":
|
||||
defaultPlayerName = subElement.GetAttributeString("name", "");
|
||||
CharacterHeadIndex = subElement.GetAttributeInt("headindex", Rand.Int(10));
|
||||
CharacterGender = subElement.GetAttributeString("gender", Rand.Range(0.0f, 1.0f) < 0.5f ? "male" : "female")
|
||||
.ToLowerInvariant() == "male" ? Gender.Male : Gender.Female;
|
||||
CharacterHeadIndex = subElement.GetAttributeInt("headindex", CharacterHeadIndex);
|
||||
if (Enum.TryParse(subElement.GetAttributeString("gender", "none"), true, out Gender g))
|
||||
{
|
||||
CharacterGender = g;
|
||||
}
|
||||
if (Enum.TryParse(subElement.GetAttributeString("race", "white"), true, out Race r))
|
||||
{
|
||||
CharacterRace = r;
|
||||
@@ -392,12 +452,6 @@ namespace Barotrauma
|
||||
CharacterMoustacheIndex = subElement.GetAttributeInt("moustacheindex", -1);
|
||||
CharacterFaceAttachmentIndex = subElement.GetAttributeInt("faceattachmentindex", -1);
|
||||
break;
|
||||
case "tutorials":
|
||||
foreach (XElement tutorialElement in subElement.Elements())
|
||||
{
|
||||
CompletedTutorialNames.Add(tutorialElement.GetAttributeString("name", ""));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,9 +463,7 @@ namespace Barotrauma
|
||||
keyMapping[(int)inputType] = new KeyOrMouse(Keys.D1);
|
||||
}
|
||||
}
|
||||
|
||||
UnsavedSettings = false;
|
||||
|
||||
|
||||
List<string> missingPackagePaths = new List<string>();
|
||||
List<ContentPackage> incompatiblePackages = new List<ContentPackage>();
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
@@ -439,6 +491,34 @@ namespace Barotrauma
|
||||
|
||||
TextManager.LoadTextPacks(SelectedContentPackages);
|
||||
|
||||
//display error messages after all content packages have been loaded
|
||||
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
|
||||
foreach (string missingPackagePath in missingPackagePaths)
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
|
||||
}
|
||||
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
|
||||
.Replace("[packagename]", incompatiblePackage.Name)
|
||||
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
|
||||
.Replace("[gameversion]", GameMain.Version.ToString()));
|
||||
}
|
||||
foreach (ContentPackage contentPackage in SelectedContentPackages)
|
||||
{
|
||||
foreach (ContentFile file in contentPackage.Files)
|
||||
{
|
||||
if (!System.IO.File.Exists(file.Path))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
ToolBox.IsProperFilenameCase(file.Path);
|
||||
}
|
||||
}
|
||||
|
||||
TextManager.LoadTextPacks(SelectedContentPackages);
|
||||
|
||||
//display error messages after all content packages have been loaded
|
||||
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
|
||||
foreach (string missingPackagePath in missingPackagePaths)
|
||||
@@ -474,19 +554,382 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//save to get rid of the invalid selected packages in the config file
|
||||
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { Save(); }
|
||||
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public KeyOrMouse KeyBind(InputType inputType)
|
||||
#region Save DefaultConfig
|
||||
private void SaveNewDefaultConfig()
|
||||
{
|
||||
return keyMapping[(int)inputType];
|
||||
XDocument doc = new XDocument();
|
||||
|
||||
if (doc.Root == null)
|
||||
{
|
||||
doc.Add(new XElement("config"));
|
||||
}
|
||||
|
||||
doc.Root.Add(
|
||||
new XAttribute("language", TextManager.Language),
|
||||
new XAttribute("masterserverurl", MasterServerUrl),
|
||||
new XAttribute("autocheckupdates", AutoCheckUpdates),
|
||||
new XAttribute("musicvolume", musicVolume),
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
new XAttribute("voicechatvolume", voiceChatVolume),
|
||||
new XAttribute("verboselogging", VerboseLogging),
|
||||
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
|
||||
new XAttribute("enablesplashscreen", EnableSplashScreen),
|
||||
new XAttribute("usesteammatchmaking", useSteamMatchmaking),
|
||||
new XAttribute("quickstartsub", QuickStartSubmarineName),
|
||||
new XAttribute("requiresteamauthentication", requireSteamAuthentication),
|
||||
new XAttribute("aimassistamount", aimAssistAmount));
|
||||
|
||||
if (!ShowUserStatisticsPrompt)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
|
||||
}
|
||||
|
||||
if (WasGameUpdated)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("wasgameupdated", true));
|
||||
}
|
||||
|
||||
XElement gMode = doc.Root.Element("graphicsmode");
|
||||
if (gMode == null)
|
||||
{
|
||||
gMode = new XElement("graphicsmode");
|
||||
doc.Root.Add(gMode);
|
||||
}
|
||||
if (GraphicsWidth == 0 || GraphicsHeight == 0)
|
||||
{
|
||||
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
|
||||
}
|
||||
else
|
||||
{
|
||||
gMode.ReplaceAttributes(
|
||||
new XAttribute("width", GraphicsWidth),
|
||||
new XAttribute("height", GraphicsHeight),
|
||||
new XAttribute("vsync", VSyncEnabled),
|
||||
new XAttribute("displaymode", windowMode));
|
||||
}
|
||||
|
||||
XElement gSettings = doc.Root.Element("graphicssettings");
|
||||
if (gSettings == null)
|
||||
{
|
||||
gSettings = new XElement("graphicssettings");
|
||||
doc.Root.Add(gSettings);
|
||||
}
|
||||
|
||||
gSettings.ReplaceAttributes(
|
||||
new XAttribute("particlelimit", ParticleLimit),
|
||||
new XAttribute("lightmapscale", LightMapScale),
|
||||
new XAttribute("specularity", SpecularityEnabled),
|
||||
new XAttribute("chromaticaberration", ChromaticAberrationEnabled),
|
||||
new XAttribute("losmode", LosMode),
|
||||
new XAttribute("hudscale", HUDScale),
|
||||
new XAttribute("inventoryscale", InventoryScale));
|
||||
|
||||
foreach (ContentPackage contentPackage in SelectedContentPackages)
|
||||
{
|
||||
if (contentPackage.Path.Contains(vanillaContentPackagePath))
|
||||
{
|
||||
doc.Root.Add(new XElement("contentpackage", new XAttribute("path", contentPackage.Path)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var keyMappingElement = new XElement("keymapping");
|
||||
doc.Root.Add(keyMappingElement);
|
||||
for (int i = 0; i < keyMapping.Length; i++)
|
||||
{
|
||||
if (keyMapping[i].MouseButton == null)
|
||||
{
|
||||
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].Key));
|
||||
}
|
||||
else
|
||||
{
|
||||
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].MouseButton));
|
||||
}
|
||||
}
|
||||
|
||||
var gameplay = new XElement("gameplay");
|
||||
var jobPreferences = new XElement("jobpreferences");
|
||||
foreach (string jobName in JobPreferences)
|
||||
{
|
||||
jobPreferences.Add(new XElement("job", new XAttribute("identifier", jobName)));
|
||||
}
|
||||
gameplay.Add(jobPreferences);
|
||||
doc.Root.Add(gameplay);
|
||||
|
||||
var playerElement = new XElement("player",
|
||||
new XAttribute("name", defaultPlayerName ?? ""),
|
||||
new XAttribute("headindex", CharacterHeadIndex),
|
||||
new XAttribute("gender", CharacterGender),
|
||||
new XAttribute("race", CharacterRace),
|
||||
new XAttribute("hairindex", CharacterHairIndex),
|
||||
new XAttribute("beardindex", CharacterBeardIndex),
|
||||
new XAttribute("moustacheindex", CharacterMoustacheIndex),
|
||||
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
|
||||
doc.Root.Add(playerElement);
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var writer = XmlWriter.Create(savePath, settings))
|
||||
{
|
||||
doc.WriteTo(writer);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving game settings failed.", e);
|
||||
GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void Save()
|
||||
#region Load PlayerConfig
|
||||
// TODO: DRY
|
||||
public void LoadPlayerConfig()
|
||||
{
|
||||
XDocument doc = XMLExtensions.LoadXml(playerSavePath);
|
||||
|
||||
if (doc == null || doc.Root == null)
|
||||
{
|
||||
ShowUserStatisticsPrompt = true;
|
||||
SaveNewPlayerConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
Language = doc.Root.GetAttributeString("language", Language);
|
||||
AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates);
|
||||
sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", true);
|
||||
|
||||
XElement graphicsMode = doc.Root.Element("graphicsmode");
|
||||
GraphicsWidth = graphicsMode.GetAttributeInt("width", GraphicsWidth);
|
||||
GraphicsHeight = graphicsMode.GetAttributeInt("height", GraphicsHeight);
|
||||
VSyncEnabled = graphicsMode.GetAttributeBool("vsync", VSyncEnabled);
|
||||
|
||||
XElement graphicsSettings = doc.Root.Element("graphicssettings");
|
||||
ParticleLimit = graphicsSettings.GetAttributeInt("particlelimit", ParticleLimit);
|
||||
LightMapScale = MathHelper.Clamp(graphicsSettings.GetAttributeFloat("lightmapscale", LightMapScale), 0.1f, 1.0f);
|
||||
SpecularityEnabled = graphicsSettings.GetAttributeBool("specularity", SpecularityEnabled);
|
||||
ChromaticAberrationEnabled = graphicsSettings.GetAttributeBool("chromaticaberration", ChromaticAberrationEnabled);
|
||||
HUDScale = graphicsSettings.GetAttributeFloat("hudscale", HUDScale);
|
||||
InventoryScale = graphicsSettings.GetAttributeFloat("inventoryscale", InventoryScale);
|
||||
var losModeStr = graphicsSettings.GetAttributeString("losmode", "Transparent");
|
||||
if (!Enum.TryParse(losModeStr, out losMode))
|
||||
{
|
||||
losMode = LosMode.Transparent;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (GraphicsWidth == 0 || GraphicsHeight == 0)
|
||||
{
|
||||
GraphicsWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
|
||||
GraphicsHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
|
||||
}
|
||||
#endif
|
||||
|
||||
var windowModeStr = graphicsMode.GetAttributeString("displaymode", "Fullscreen");
|
||||
if (!Enum.TryParse(windowModeStr, out windowMode))
|
||||
{
|
||||
windowMode = WindowMode.Fullscreen;
|
||||
}
|
||||
|
||||
XElement audioSettings = doc.Root.Element("audio");
|
||||
if (audioSettings != null)
|
||||
{
|
||||
SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume);
|
||||
MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume);
|
||||
VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
|
||||
string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled");
|
||||
VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", "");
|
||||
NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45);
|
||||
var voiceSetting = VoiceMode.Disabled;
|
||||
if (Enum.TryParse(voiceSettingStr, out voiceSetting))
|
||||
{
|
||||
VoiceSetting = voiceSetting;
|
||||
}
|
||||
}
|
||||
|
||||
useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", useSteamMatchmaking);
|
||||
requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", requireSteamAuthentication);
|
||||
|
||||
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
|
||||
|
||||
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount);
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "keymapping":
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType))
|
||||
{
|
||||
if (int.TryParse(attribute.Value.ToString(), out int mouseButton))
|
||||
{
|
||||
keyMapping[(int)inputType] = new KeyOrMouse(mouseButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Enum.TryParse(attribute.Value.ToString(), true, out Keys key))
|
||||
{
|
||||
keyMapping[(int)inputType] = new KeyOrMouse(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "gameplay":
|
||||
jobPreferences = new List<string>();
|
||||
foreach (XElement ele in subElement.Element("jobpreferences").Elements("job"))
|
||||
{
|
||||
string jobIdentifier = ele.GetAttributeString("identifier", "");
|
||||
if (string.IsNullOrEmpty(jobIdentifier)) continue;
|
||||
jobPreferences.Add(jobIdentifier);
|
||||
}
|
||||
break;
|
||||
case "player":
|
||||
defaultPlayerName = subElement.GetAttributeString("name", defaultPlayerName);
|
||||
CharacterHeadIndex = subElement.GetAttributeInt("headindex", CharacterHeadIndex);
|
||||
if (Enum.TryParse(subElement.GetAttributeString("gender", "none"), true, out Gender g))
|
||||
{
|
||||
CharacterGender = g;
|
||||
}
|
||||
if (Enum.TryParse(subElement.GetAttributeString("race", "white"), true, out Race r))
|
||||
{
|
||||
CharacterRace = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterRace = Race.White;
|
||||
}
|
||||
CharacterHairIndex = subElement.GetAttributeInt("hairindex", CharacterHairIndex);
|
||||
CharacterBeardIndex = subElement.GetAttributeInt("beardindex", CharacterBeardIndex);
|
||||
CharacterMoustacheIndex = subElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex);
|
||||
CharacterFaceAttachmentIndex = subElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex);
|
||||
break;
|
||||
case "tutorials":
|
||||
foreach (XElement tutorialElement in subElement.Elements())
|
||||
{
|
||||
CompletedTutorialNames.Add(tutorialElement.GetAttributeString("name", ""));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
|
||||
{
|
||||
if (keyMapping[(int)inputType] == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Key binding for the input type \"" + inputType + " not set!");
|
||||
keyMapping[(int)inputType] = new KeyOrMouse(Keys.D1);
|
||||
}
|
||||
}
|
||||
|
||||
UnsavedSettings = false;
|
||||
|
||||
selectedContentPackagePaths = new HashSet<string>();
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "contentpackage":
|
||||
string path = System.IO.Path.GetFullPath(subElement.GetAttributeString("path", ""));
|
||||
selectedContentPackagePaths.Add(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LoadContentPackages(selectedContentPackagePaths);
|
||||
}
|
||||
|
||||
public void ReloadContentPackages()
|
||||
{
|
||||
LoadContentPackages(selectedContentPackagePaths);
|
||||
}
|
||||
|
||||
private void LoadContentPackages(IEnumerable<string> contentPackagePaths)
|
||||
{
|
||||
var missingPackagePaths = new List<string>();
|
||||
var incompatiblePackages = new List<ContentPackage>();
|
||||
SelectedContentPackages.Clear();
|
||||
foreach (string path in contentPackagePaths)
|
||||
{
|
||||
var matchingContentPackage = ContentPackage.List.Find(cp => System.IO.Path.GetFullPath(cp.Path) == path);
|
||||
|
||||
if (matchingContentPackage == null)
|
||||
{
|
||||
missingPackagePaths.Add(path);
|
||||
}
|
||||
else if (!matchingContentPackage.IsCompatible())
|
||||
{
|
||||
incompatiblePackages.Add(matchingContentPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedContentPackages.Add(matchingContentPackage);
|
||||
}
|
||||
}
|
||||
|
||||
TextManager.LoadTextPacks(SelectedContentPackages);
|
||||
|
||||
foreach (ContentPackage contentPackage in SelectedContentPackages)
|
||||
{
|
||||
foreach (ContentFile file in contentPackage.Files)
|
||||
{
|
||||
if (!System.IO.File.Exists(file.Path))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
ToolBox.IsProperFilenameCase(file.Path);
|
||||
}
|
||||
}
|
||||
if (!SelectedContentPackages.Any())
|
||||
{
|
||||
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
|
||||
if (availablePackage != null)
|
||||
{
|
||||
SelectedContentPackages.Add(availablePackage);
|
||||
}
|
||||
}
|
||||
|
||||
//save to get rid of the invalid selected packages in the config file
|
||||
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
|
||||
|
||||
//display error messages after all content packages have been loaded
|
||||
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
|
||||
foreach (string missingPackagePath in missingPackagePaths)
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
|
||||
}
|
||||
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
|
||||
.Replace("[packagename]", incompatiblePackage.Name)
|
||||
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
|
||||
.Replace("[gameversion]", GameMain.Version.ToString()));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Save PlayerConfig
|
||||
public void SaveNewPlayerConfig()
|
||||
{
|
||||
XDocument doc = new XDocument();
|
||||
UnsavedSettings = false;
|
||||
|
||||
if (doc.Root == null)
|
||||
{
|
||||
@@ -505,6 +948,7 @@ namespace Barotrauma
|
||||
new XAttribute("usesteammatchmaking", useSteamMatchmaking),
|
||||
new XAttribute("quickstartsub", QuickStartSubmarineName),
|
||||
new XAttribute("requiresteamauthentication", requireSteamAuthentication),
|
||||
new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems),
|
||||
new XAttribute("aimassistamount", aimAssistAmount));
|
||||
|
||||
if (!ShowUserStatisticsPrompt)
|
||||
@@ -512,11 +956,6 @@ namespace Barotrauma
|
||||
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
|
||||
}
|
||||
|
||||
if (WasGameUpdated)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("wasgameupdated", true));
|
||||
}
|
||||
|
||||
XElement gMode = doc.Root.Element("graphicsmode");
|
||||
if (gMode == null)
|
||||
{
|
||||
@@ -536,6 +975,19 @@ namespace Barotrauma
|
||||
new XAttribute("vsync", VSyncEnabled),
|
||||
new XAttribute("displaymode", windowMode));
|
||||
}
|
||||
|
||||
XElement audio = doc.Root.Element("audio");
|
||||
if (audio == null)
|
||||
{
|
||||
audio = new XElement("audio");
|
||||
doc.Root.Add(audio);
|
||||
}
|
||||
audio.ReplaceAttributes(
|
||||
new XAttribute("musicvolume", musicVolume),
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
new XAttribute("voicesetting", VoiceSetting),
|
||||
new XAttribute("voicecapturedevice", VoiceCaptureDevice ?? ""),
|
||||
new XAttribute("noisegatethreshold", NoiseGateThreshold));
|
||||
|
||||
XElement gSettings = doc.Root.Element("graphicssettings");
|
||||
if (gSettings == null)
|
||||
@@ -619,9 +1071,35 @@ namespace Barotrauma
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
#if CLIENT
|
||||
if (Tutorial.Tutorials != null)
|
||||
{
|
||||
foreach (Tutorial tutorial in Tutorial.Tutorials)
|
||||
{
|
||||
if (tutorial.Completed && !CompletedTutorialNames.Contains(tutorial.Name))
|
||||
{
|
||||
CompletedTutorialNames.Add(tutorial.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
var tutorialElement = new XElement("tutorials");
|
||||
foreach (string tutorialName in CompletedTutorialNames)
|
||||
{
|
||||
tutorialElement.Add(new XElement("Tutorial", new XAttribute("name", tutorialName)));
|
||||
}
|
||||
doc.Root.Add(tutorialElement);
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = true,
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var writer = XmlWriter.Create(FilePath, settings))
|
||||
using (var writer = XmlWriter.Create(playerSavePath, settings))
|
||||
{
|
||||
doc.WriteTo(writer);
|
||||
writer.Flush();
|
||||
@@ -634,5 +1112,17 @@ namespace Barotrauma
|
||||
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void ResetToDefault()
|
||||
{
|
||||
LoadDefaultConfig();
|
||||
SaveNewPlayerConfig();
|
||||
}
|
||||
|
||||
public KeyOrMouse KeyBind(InputType inputType)
|
||||
{
|
||||
return keyMapping[(int)inputType];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,10 @@ namespace Barotrauma
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
#if CLIENT
|
||||
//clients don't create items until the server says so
|
||||
if (GameMain.Client != null) return;
|
||||
#endif
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -86,7 +88,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
public int FindLimbSlot(InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
|
||||
@@ -167,6 +167,8 @@ namespace Barotrauma.Items.Components
|
||||
DockingTarget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
target.InitializeLinks();
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
@@ -209,15 +211,19 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
CreateJoint(false);
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Lock(bool isNetworkMessage, bool forcePosition = false)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !isNetworkMessage) return;
|
||||
#endif
|
||||
|
||||
if (DockingTarget == null)
|
||||
{
|
||||
@@ -249,10 +255,12 @@ namespace Barotrauma.Items.Components
|
||||
ConnectWireBetweenPorts();
|
||||
CreateJoint(true);
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -730,10 +738,12 @@ namespace Barotrauma.Items.Components
|
||||
bodies = null;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -819,8 +829,13 @@ namespace Barotrauma.Items.Components
|
||||
gap?.Remove(); gap = null;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
private bool initialized = false;
|
||||
private void InitializeLinks()
|
||||
{
|
||||
if (initialized) { return; }
|
||||
initialized = true;
|
||||
|
||||
float closestDist = 30.0f * 30.0f;
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != item.Submarine) continue;
|
||||
@@ -828,10 +843,11 @@ namespace Barotrauma.Items.Components
|
||||
var doorComponent = it.GetComponent<Door>();
|
||||
if (doorComponent == null) continue;
|
||||
|
||||
if (Vector2.Distance(item.Position, doorComponent.Item.Position) < Submarine.GridSize.X)
|
||||
float distSqr = Vector2.Distance(item.Position, it.Position);
|
||||
if (distSqr < closestDist)
|
||||
{
|
||||
this.door = doorComponent;
|
||||
break;
|
||||
door = doorComponent;
|
||||
closestDist = distSqr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,9 +868,20 @@ namespace Barotrauma.Items.Components
|
||||
gap.Remove();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
InitializeLinks();
|
||||
|
||||
if (!item.linkedTo.Any()) return;
|
||||
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
{
|
||||
Item linkedItem = entity as Item;
|
||||
if (linkedItem == null) continue;
|
||||
if (linkedItem == null) { continue; }
|
||||
|
||||
var dockingPort = linkedItem.GetComponent<DockingPort>();
|
||||
if (dockingPort != null)
|
||||
@@ -866,7 +893,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return;
|
||||
#endif
|
||||
|
||||
bool wasDocked = docked;
|
||||
DockingPort prevDockingTarget = DockingTarget;
|
||||
@@ -882,6 +911,7 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (sender != null && docked != wasDocked)
|
||||
{
|
||||
if (docked)
|
||||
@@ -895,6 +925,7 @@ namespace Barotrauma.Items.Components
|
||||
GameServer.Log(sender.LogName + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
|
||||
@@ -16,35 +16,24 @@ namespace Barotrauma.Items.Components
|
||||
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private Gap linkedGap;
|
||||
|
||||
private Rectangle window;
|
||||
|
||||
private bool isOpen;
|
||||
|
||||
private float openState;
|
||||
|
||||
private PhysicsBody body;
|
||||
|
||||
private Sprite doorSprite, weldedSprite, brokenSprite;
|
||||
private bool scaleBrokenSprite, fadeBrokenSprite;
|
||||
|
||||
private bool isHorizontal;
|
||||
private bool createdNewGap;
|
||||
private bool autoOrientGap;
|
||||
|
||||
private bool createdNewGap;
|
||||
private bool autoOrientGap;
|
||||
|
||||
private bool isStuck;
|
||||
|
||||
private bool? predictedState;
|
||||
private float resetPredictionTimer;
|
||||
|
||||
private Rectangle doorRect;
|
||||
|
||||
private bool isBroken;
|
||||
|
||||
//openState when the vertices of the convex hull were last calculated
|
||||
private float lastConvexHullState;
|
||||
|
||||
|
||||
public bool IsBroken
|
||||
{
|
||||
get { return isBroken; }
|
||||
@@ -63,10 +52,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public PhysicsBody Body
|
||||
{
|
||||
get { return body; }
|
||||
}
|
||||
public PhysicsBody Body { get; private set; }
|
||||
|
||||
private float stuck;
|
||||
[Serialize(0.0f, false)]
|
||||
@@ -82,10 +68,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool? PredictedState
|
||||
{
|
||||
get { return predictedState; }
|
||||
}
|
||||
public bool? PredictedState { get; private set; }
|
||||
|
||||
public Gap LinkedGap
|
||||
{
|
||||
@@ -98,12 +81,12 @@ namespace Barotrauma.Items.Components
|
||||
linkedGap = e as Gap;
|
||||
if (linkedGap != null)
|
||||
{
|
||||
linkedGap.PassAmbientLight = window != Rectangle.Empty;
|
||||
linkedGap.PassAmbientLight = Window != Rectangle.Empty;
|
||||
return linkedGap;
|
||||
}
|
||||
}
|
||||
Rectangle rect = item.Rect;
|
||||
if (isHorizontal)
|
||||
if (IsHorizontal)
|
||||
{
|
||||
rect.Y += 5;
|
||||
rect.Height += 10;
|
||||
@@ -114,10 +97,10 @@ namespace Barotrauma.Items.Components
|
||||
rect.Width += 10;
|
||||
}
|
||||
|
||||
linkedGap = new Gap(rect, !isHorizontal, Item.Submarine)
|
||||
linkedGap = new Gap(rect, !IsHorizontal, Item.Submarine)
|
||||
{
|
||||
Submarine = item.Submarine,
|
||||
PassAmbientLight = window != Rectangle.Empty,
|
||||
PassAmbientLight = Window != Rectangle.Empty,
|
||||
Open = openState
|
||||
};
|
||||
item.linkedTo.Add(linkedGap);
|
||||
@@ -126,18 +109,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
}
|
||||
|
||||
public bool IsHorizontal { get; private set; }
|
||||
|
||||
[Serialize("0.0,0.0,0.0,0.0", false)]
|
||||
public Rectangle Window
|
||||
{
|
||||
get { return window; }
|
||||
set { window = value; }
|
||||
}
|
||||
|
||||
public Rectangle Window { get; set; }
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
public bool IsOpen
|
||||
{
|
||||
@@ -156,7 +132,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
float size = isHorizontal ? item.Rect.Width : item.Rect.Height;
|
||||
float size = IsHorizontal ? item.Rect.Width : item.Rect.Height;
|
||||
if (Math.Abs(lastConvexHullState - openState) * size < 5.0f) { return; }
|
||||
UpdateConvexHulls();
|
||||
lastConvexHullState = openState;
|
||||
@@ -174,7 +150,7 @@ namespace Barotrauma.Items.Components
|
||||
public Door(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
isHorizontal = element.GetAttributeBool("horizontal", false);
|
||||
IsHorizontal = element.GetAttributeBool("horizontal", false);
|
||||
canBePicked = element.GetAttributeBool("canbepicked", false);
|
||||
autoOrientGap = element.GetAttributeBool("autoorientgap", false);
|
||||
|
||||
@@ -203,7 +179,7 @@ namespace Barotrauma.Items.Components
|
||||
(int)(doorSprite.size.X * item.Scale),
|
||||
(int)(doorSprite.size.Y * item.Scale));
|
||||
|
||||
body = new PhysicsBody(
|
||||
Body = new PhysicsBody(
|
||||
ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)),
|
||||
ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)),
|
||||
0.0f,
|
||||
@@ -214,7 +190,7 @@ namespace Barotrauma.Items.Components
|
||||
BodyType = BodyType.Static,
|
||||
Friction = 0.5f
|
||||
};
|
||||
body.SetTransform(
|
||||
Body.SetTransform(
|
||||
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
|
||||
0.0f);
|
||||
|
||||
@@ -225,7 +201,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
body?.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
@@ -249,7 +225,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (item.Condition <= 0.0f) return true; //repairs
|
||||
|
||||
SetState(predictedState == null ? !isOpen : !predictedState.Value, false, true); //crowbar function
|
||||
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnPicked, item.WorldPosition, picker);
|
||||
#endif
|
||||
@@ -267,7 +243,7 @@ namespace Barotrauma.Items.Components
|
||||
if (isBroken)
|
||||
{
|
||||
//the door has to be restored to 50% health before collision detection on the body is re-enabled
|
||||
if (item.Condition > 50.0f)
|
||||
if (item.Condition > item.Prefab.Health / 2.0f)
|
||||
{
|
||||
IsBroken = false;
|
||||
}
|
||||
@@ -277,20 +253,20 @@ namespace Barotrauma.Items.Components
|
||||
bool isClosing = false;
|
||||
if (!isStuck)
|
||||
{
|
||||
if (predictedState == null)
|
||||
if (PredictedState == null)
|
||||
{
|
||||
OpenState += deltaTime * (isOpen ? 2.0f : -2.0f);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !isOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenState += deltaTime * ((bool)predictedState ? 2.0f : -2.0f);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !(bool)predictedState;
|
||||
OpenState += deltaTime * ((bool)PredictedState ? 2.0f : -2.0f);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !(bool)PredictedState;
|
||||
|
||||
resetPredictionTimer -= deltaTime;
|
||||
if (resetPredictionTimer <= 0.0f)
|
||||
{
|
||||
predictedState = null;
|
||||
PredictedState = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +279,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Enabled = Impassable || openState < 1.0f;
|
||||
Body.Enabled = Impassable || openState < 1.0f;
|
||||
}
|
||||
|
||||
//don't use the predicted state here, because it might set
|
||||
@@ -320,7 +296,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!Impassable)
|
||||
{
|
||||
body.FarseerBody.IsSensor = false;
|
||||
Body.FarseerBody.IsSensor = false;
|
||||
}
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
@@ -334,7 +310,7 @@ namespace Barotrauma.Items.Components
|
||||
//because otherwise repairtool raycasts won't hit it
|
||||
if (!Impassable)
|
||||
{
|
||||
body.FarseerBody.IsSensor = true;
|
||||
Body.FarseerBody.IsSensor = true;
|
||||
}
|
||||
linkedGap.Open = 1.0f;
|
||||
IsOpen = false;
|
||||
@@ -354,7 +330,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2[] corners = GetConvexHullCorners(Rectangle.Empty);
|
||||
|
||||
convexHull = new ConvexHull(corners, Color.Black, item);
|
||||
if (window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item);
|
||||
if (Window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item);
|
||||
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
@@ -364,10 +340,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
|
||||
if (body != null)
|
||||
if (Body != null)
|
||||
{
|
||||
body.Remove();
|
||||
body = null;
|
||||
Body.Remove();
|
||||
Body = null;
|
||||
}
|
||||
|
||||
//no need to remove the gap if we're unloading the whole submarine
|
||||
@@ -399,7 +375,7 @@ namespace Barotrauma.Items.Components
|
||||
//push characters out of the doorway when the door is closing/opening
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y));
|
||||
|
||||
Vector2 currSize = isHorizontal ?
|
||||
Vector2 currSize = IsHorizontal ?
|
||||
new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y * item.Scale) :
|
||||
new Vector2(doorSprite.size.X * item.Scale, item.Rect.Height * (1.0f - openState));
|
||||
|
||||
@@ -417,7 +393,7 @@ namespace Barotrauma.Items.Components
|
||||
" Remoteplayer: " + c.IsRemotePlayer);
|
||||
continue;
|
||||
}
|
||||
int dir = isHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X);
|
||||
int dir = IsHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X);
|
||||
|
||||
List<PhysicsBody> bodies = c.AnimController.Limbs.Select(l => l.body).ToList();
|
||||
bodies.Add(c.AnimController.Collider);
|
||||
@@ -435,7 +411,7 @@ namespace Barotrauma.Items.Components
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isHorizontal)
|
||||
if (IsHorizontal)
|
||||
{
|
||||
if (body.SimPosition.X < simPos.X || body.SimPosition.X > simPos.X + simSize.X) continue;
|
||||
diff = body.SimPosition.Y - item.SimPosition.Y;
|
||||
@@ -452,7 +428,7 @@ namespace Barotrauma.Items.Components
|
||||
SoundPlayer.PlayDamageSound("LimbBlunt", 1.0f, body);
|
||||
#endif
|
||||
|
||||
if (isHorizontal)
|
||||
if (IsHorizontal)
|
||||
{
|
||||
body.SetTransform(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * simSize.Y * 2.0f), body.Rotation);
|
||||
body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 2.0f));
|
||||
@@ -464,7 +440,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (isHorizontal)
|
||||
if (IsHorizontal)
|
||||
{
|
||||
if (Math.Abs(body.SimPosition.Y - item.SimPosition.Y) > simSize.Y * 0.5f) continue;
|
||||
|
||||
@@ -486,7 +462,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (isStuck) return;
|
||||
|
||||
bool wasOpen = predictedState == null ? isOpen : predictedState.Value;
|
||||
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;
|
||||
|
||||
if (connection.Name == "toggle")
|
||||
{
|
||||
@@ -497,66 +473,19 @@ namespace Barotrauma.Items.Components
|
||||
SetState(signal != "0", false, true);
|
||||
}
|
||||
|
||||
bool newState = predictedState == null ? isOpen : predictedState.Value;
|
||||
if (sender != null && wasOpen != newState)
|
||||
#if SERVER
|
||||
if (sender != null && wasOpen != isOpen)
|
||||
{
|
||||
GameServer.Log(sender.LogName + (newState ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(sender.LogName + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false)
|
||||
{
|
||||
if (isStuck ||
|
||||
(predictedState == null && isOpen == open) ||
|
||||
(predictedState != null && isOpen == predictedState.Value && isOpen == open)) return;
|
||||
|
||||
if (GameMain.Client != null && !isNetworkMessage)
|
||||
{
|
||||
bool stateChanged = open != predictedState;
|
||||
|
||||
//clients can "predict" that the door opens/closes when a signal is received
|
||||
//the prediction will be reset after 1 second, setting the door to a state
|
||||
//sent by the server, or reverting it back to its old state if no msg from server was received
|
||||
predictedState = open;
|
||||
resetPredictionTimer = CorrectionDelay;
|
||||
#if CLIENT
|
||||
if (stateChanged) PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
isOpen = open;
|
||||
#if CLIENT
|
||||
if (!isNetworkMessage || open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
}
|
||||
|
||||
//opening a partially stuck door makes it less stuck
|
||||
if (isOpen) stuck = MathHelper.Clamp(stuck - 30.0f, 0.0f, 100.0f);
|
||||
|
||||
if (sendNetworkMessage)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
public void TrySetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false)
|
||||
{
|
||||
base.ServerWrite(msg, c, extraData);
|
||||
|
||||
msg.Write(isOpen);
|
||||
msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8);
|
||||
SetState(open, isNetworkMessage, sendNetworkMessage);
|
||||
}
|
||||
|
||||
public override void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
|
||||
{
|
||||
base.ClientRead(type, msg, sendingTime);
|
||||
|
||||
SetState(msg.ReadBoolean(), true);
|
||||
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
|
||||
predictedState = null;
|
||||
}
|
||||
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,9 @@ namespace Barotrauma.Items.Components
|
||||
item.body.Enabled = true;
|
||||
IsActive = true;
|
||||
|
||||
#if SERVER
|
||||
if (!alreadySelected) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,8 +299,9 @@ namespace Barotrauma.Items.Components
|
||||
if (picker == null) return;
|
||||
|
||||
picker.DeselectItem(item);
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log(character.LogName + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
|
||||
item.body.Enabled = false;
|
||||
IsActive = false;
|
||||
@@ -354,6 +357,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
DeattachFromWall();
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && attachable)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
@@ -362,6 +366,7 @@ namespace Barotrauma.Items.Components
|
||||
GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -426,11 +431,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!character.IsKeyDown(InputType.Aim)) return false;
|
||||
if (!CanBeAttached()) return false;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
item.Drop();
|
||||
}
|
||||
|
||||
@@ -471,7 +478,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip();
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
@@ -507,7 +514,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected void Flip(Item item)
|
||||
public void Flip()
|
||||
{
|
||||
handlePos[0].X = -handlePos[0].X;
|
||||
handlePos[1].X = -handlePos[1].X;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
deattachTimer = Math.Max(0.0f, value);
|
||||
//clients don't deattach the item until the server says so (handled in ClientRead)
|
||||
if (GameMain.Client == null && deattachTimer >= DeattachDuration)
|
||||
if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && deattachTimer >= DeattachDuration)
|
||||
{
|
||||
holdable.DeattachFromWall();
|
||||
}
|
||||
@@ -80,6 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
trigger.FarseerBody.IsSensor = true;
|
||||
trigger.FarseerBody.IsStatic = true;
|
||||
trigger.FarseerBody.CollisionCategories = Physics.CollisionWall;
|
||||
trigger.FarseerBody.CollidesWith = Physics.CollisionNone;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip();
|
||||
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
@@ -294,8 +294,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Client != null) return true;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return true;
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
|
||||
{
|
||||
|
||||
@@ -308,13 +309,14 @@ namespace Barotrauma.Items.Components
|
||||
});
|
||||
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Length > 0)
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
logStr += "(" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + targetCharacter.LogName + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
{
|
||||
|
||||
@@ -66,7 +66,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (picker.PickingItem == null && PickingTime <= float.MaxValue)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
|
||||
}
|
||||
return false;
|
||||
@@ -143,7 +145,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
StopPicking(picker);
|
||||
|
||||
if (!picker.IsRemotePlayer || GameMain.Server != null) OnPicked(picker);
|
||||
bool isNotRemote = true;
|
||||
#if CLIENT
|
||||
isNotRemote = !picker.IsRemotePlayer;
|
||||
#endif
|
||||
if (isNotRemote) OnPicked(picker);
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Projectile projectile = null;
|
||||
Item[] containedItems = item.ContainedItems;
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems == null) return true;
|
||||
|
||||
foreach (Item item in containedItems)
|
||||
@@ -105,18 +105,29 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
Item[] containedSubItems = item.ContainedItems;
|
||||
foreach (Item subItem in containedSubItems)
|
||||
projectile = item.GetComponent<Projectile>();
|
||||
if (projectile != null) break;
|
||||
}
|
||||
//projectile not found, see if one of the contained items contains projectiles
|
||||
if (projectile == null)
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
projectile = subItem.GetComponent<Projectile>();
|
||||
|
||||
//apply OnUse statuseffects to the container in case it has to react to it somehow
|
||||
//(play a sound, spawn more projectiles, reduce condition...)
|
||||
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
|
||||
if (projectile != null) break;
|
||||
var containedSubItems = item.ContainedItems;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
{
|
||||
projectile = subItem.GetComponent<Projectile>();
|
||||
|
||||
//apply OnUse statuseffects to the container in case it has to react to it somehow
|
||||
//(play a sound, spawn more projectiles, reduce condition...)
|
||||
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
|
||||
if (projectile != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectile == null) return true;
|
||||
|
||||
float spread = MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
|
||||
|
||||
@@ -14,52 +14,35 @@ namespace Barotrauma.Items.Components
|
||||
partial class RepairTool : ItemComponent
|
||||
{
|
||||
private readonly List<string> fixableEntities;
|
||||
|
||||
private float range;
|
||||
|
||||
private Vector2 pickedPosition;
|
||||
|
||||
private Vector2 barrelPos;
|
||||
|
||||
private float activeTimer;
|
||||
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = value; }
|
||||
}
|
||||
public float Range { get; set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float StructureFixAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float LimbFixAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[Serialize(0.0f, false)]
|
||||
public float ExtinguishAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0.0,0.0", false)]
|
||||
public Vector2 BarrelPos
|
||||
{
|
||||
get { return barrelPos; }
|
||||
set { barrelPos = value; }
|
||||
}
|
||||
public Vector2 BarrelPos { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool RepairThroughWalls { get; set; }
|
||||
|
||||
public Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
{
|
||||
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
Vector2 flippedPos = barrelPos;
|
||||
Vector2 flippedPos = BarrelPos;
|
||||
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
|
||||
return (Vector2.Transform(flippedPos, bodyTransform));
|
||||
}
|
||||
@@ -70,6 +53,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
this.item = item;
|
||||
|
||||
if (element.Attribute("limbfixamount") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item \"" + item.Name + "\" - RepairTool damage should be configured using a StatusEffect with Afflictions, not the limbfixamount attribute.");
|
||||
}
|
||||
|
||||
fixableEntities = new List<string>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -116,7 +104,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 targetPosition = item.WorldPosition;
|
||||
targetPosition += new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation)) * range * item.body.Dir;
|
||||
(float)Math.Sin(item.body.Rotation)) * Range * item.body.Dir;
|
||||
|
||||
List<Body> ignoredBodies = new List<Body>();
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
@@ -154,9 +142,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
||||
{
|
||||
Body targetBody = Submarine.PickBody(rayStart, rayEnd, ignoredBodies,
|
||||
Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair, false);
|
||||
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
|
||||
if (RepairThroughWalls)
|
||||
{
|
||||
var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false);
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
FixBody(user, deltaTime, degreeOfSuccess, body);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FixBody(user, deltaTime, degreeOfSuccess, Submarine.PickBody(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false));
|
||||
}
|
||||
|
||||
if (ExtinguishAmount > 0.0f && item.CurrentHull != null)
|
||||
{
|
||||
List<FireSource> fireSourcesInRange = new List<FireSource>();
|
||||
@@ -164,7 +163,7 @@ namespace Barotrauma.Items.Components
|
||||
for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f)
|
||||
{
|
||||
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * x);
|
||||
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
|
||||
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
|
||||
|
||||
Hull hull = Hull.FindHull(displayPos, item.CurrentHull);
|
||||
if (hull == null) continue;
|
||||
@@ -182,11 +181,14 @@ namespace Barotrauma.Items.Components
|
||||
fs.Extinguish(deltaTime, ExtinguishAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetBody == null || targetBody.UserData == null) return;
|
||||
private void FixBody(Character user, float deltaTime, float degreeOfSuccess, Body targetBody)
|
||||
{
|
||||
if (targetBody?.UserData == null) { return; }
|
||||
|
||||
pickedPosition = Submarine.LastPickedPosition;
|
||||
|
||||
|
||||
if (targetBody.UserData is Structure targetStructure)
|
||||
{
|
||||
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) return;
|
||||
@@ -214,20 +216,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (targetBody.UserData is Character targetCharacter)
|
||||
{
|
||||
Vector2 hitPos = ConvertUnits.ToDisplayUnits(pickedPosition);
|
||||
if (targetCharacter.Submarine != null) hitPos += targetCharacter.Submarine.Position;
|
||||
|
||||
targetCharacter.LastDamageSource = item;
|
||||
targetCharacter.AddDamage(hitPos,
|
||||
new List<Affliction>() { AfflictionPrefab.Burn.Instantiate(-LimbFixAmount * degreeOfSuccess, user) }, 0.0f, false, 0.0f, user);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List<ISerializableEntity>() { targetCharacter });
|
||||
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
|
||||
}
|
||||
else if (targetBody.UserData is Limb targetLimb)
|
||||
{
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
targetLimb.character.DamageLimb(targetLimb.WorldPosition, targetLimb,
|
||||
new List<Affliction>() { AfflictionPrefab.Burn.Instantiate(-LimbFixAmount * degreeOfSuccess, user) }, 0.0f, false, 0.0f, user);
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List<ISerializableEntity>() { targetLimb.character, targetLimb });
|
||||
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
|
||||
}
|
||||
else if (targetBody.UserData is Item targetItem)
|
||||
@@ -236,7 +232,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float prevCondition = targetItem.Condition;
|
||||
|
||||
ApplyStatusEffectsOnTarget(deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
||||
|
||||
var levelResource = targetItem.GetComponent<LevelResource>();
|
||||
if (levelResource != null && levelResource.IsActive &&
|
||||
@@ -254,6 +250,11 @@ namespace Barotrauma.Items.Components
|
||||
FixItemProjSpecific(user, deltaTime, targetItem, prevCondition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
|
||||
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
|
||||
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition);
|
||||
|
||||
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
|
||||
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
|
||||
@@ -267,7 +268,7 @@ namespace Barotrauma.Items.Components
|
||||
float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition);
|
||||
|
||||
//too far away -> consider this done and hope the AI is smart enough to move closer
|
||||
if (dist > range * 5.0f) return true;
|
||||
if (dist > Range * 5.0f) return true;
|
||||
|
||||
Vector2 gapDiff = leak.WorldPosition - item.WorldPosition;
|
||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
||||
@@ -277,13 +278,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
//steer closer if almost in range
|
||||
if (dist > range)
|
||||
if (dist > Range)
|
||||
{
|
||||
Vector2 standPos = leak.IsHorizontal ?
|
||||
new Vector2(Math.Sign(-gapDiff.X), 0.0f)
|
||||
: new Vector2(0.0f, Math.Sign(-gapDiff.Y) * 0.5f);
|
||||
|
||||
standPos = leak.WorldPosition + standPos * range;
|
||||
standPos = leak.WorldPosition + standPos * Range;
|
||||
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f);
|
||||
}
|
||||
@@ -315,15 +316,14 @@ namespace Barotrauma.Items.Components
|
||||
return leakFixed;
|
||||
}
|
||||
|
||||
private void ApplyStatusEffectsOnTarget(float deltaTime, ActionType actionType, List<ISerializableEntity> targets)
|
||||
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, IEnumerable<ISerializableEntity> targets)
|
||||
{
|
||||
if (statusEffectLists == null) return;
|
||||
|
||||
List<StatusEffect> statusEffects;
|
||||
if (!statusEffectLists.TryGetValue(actionType, out statusEffects)) return;
|
||||
if (statusEffectLists == null) { return; }
|
||||
if (!statusEffectLists.TryGetValue(actionType, out List<StatusEffect> statusEffects)) { return; }
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.SetUser(user);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(actionType, deltaTime, item, targets);
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip();
|
||||
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
@@ -101,7 +101,9 @@ namespace Barotrauma.Items.Components
|
||||
//throw upwards if cursor is at the position of the character
|
||||
if (!MathUtils.IsValid(throwVector)) throwVector = Vector2.UnitY;
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
|
||||
item.Drop();
|
||||
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
|
||||
|
||||
@@ -14,6 +14,11 @@ namespace Barotrauma.Items.Components
|
||||
interface IDrawableComponent
|
||||
{
|
||||
#if CLIENT
|
||||
/// <summary>
|
||||
/// The extents of the sprites or other graphics this component needs to draw. Used to determine which items are visible on the screen.
|
||||
/// </summary>
|
||||
Vector2 DrawSize { get; }
|
||||
|
||||
void Draw(SpriteBatch spriteBatch, bool editing);
|
||||
#endif
|
||||
}
|
||||
@@ -98,12 +103,11 @@ namespace Barotrauma.Items.Components
|
||||
drawable = value;
|
||||
if (drawable)
|
||||
{
|
||||
if (!item.drawableComponents.Contains((IDrawableComponent)this))
|
||||
item.drawableComponents.Add((IDrawableComponent)this);
|
||||
item.EnableDrawableComponent((IDrawableComponent)this);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.drawableComponents.Remove((IDrawableComponent)this);
|
||||
item.DisableDrawableComponent((IDrawableComponent)this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,7 +315,7 @@ namespace Barotrauma.Items.Components
|
||||
if (ic == null) break;
|
||||
|
||||
ic.Parent = this;
|
||||
item.components.Add(ic);
|
||||
item.AddComponent(ic);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -531,6 +535,7 @@ namespace Barotrauma.Items.Components
|
||||
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return 0.0f;
|
||||
}
|
||||
float average = skillSuccessSum / requiredSkills.Count;
|
||||
|
||||
float skillSuccessSum = 0.0f;
|
||||
for (int i = 0; i < requiredSkills.Count; i++)
|
||||
@@ -623,7 +628,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (XAttribute attribute in componentElement.Attributes())
|
||||
{
|
||||
if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
|
||||
property.TrySetValue(attribute.Value);
|
||||
property.TrySetValue(this, attribute.Value);
|
||||
}
|
||||
#if CLIENT
|
||||
string msg = TextManager.Get(Msg, true);
|
||||
|
||||
@@ -141,6 +141,13 @@ namespace Barotrauma.Items.Components
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(item.WorldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +169,24 @@ namespace Barotrauma.Items.Components
|
||||
return base.Select(character);
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (item.Container != null) { return false; }
|
||||
|
||||
if (AutoInteractWithContained)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
{
|
||||
if (contained == null) continue;
|
||||
if (contained.TryInteract(character))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.Select(character);
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (AutoInteractWithContained)
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
namespace Barotrauma.Items.Components
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemLabel : ItemComponent, IDrawableComponent
|
||||
{
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
|
||||
{
|
||||
switch (connection.Name)
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
MoveInputQueue();
|
||||
@@ -109,7 +109,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void PutItemsToLinkedContainer()
|
||||
{
|
||||
if (GameMain.Client != null) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (outputContainer.Inventory.Items.All(it => it == null)) return;
|
||||
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
@@ -153,10 +153,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
IsActive = active;
|
||||
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!IsActive) { progressState = 0.0f; }
|
||||
|
||||
if (!IsActive) { progressState = 0.0f; }
|
||||
|
||||
@@ -174,22 +178,5 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
inputContainer.Inventory.Locked = IsActive;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
bool active = msg.ReadBoolean();
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
SetActive(active, c.Character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
currPowerConsumption = Math.Abs(targetForce) / 100.0f * powerConsumption;
|
||||
//pumps consume more power when in a bad condition
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
if (powerConsumption == 0.0f) voltage = 1.0f;
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Vector2 currForce = new Vector2((force / 100.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f);
|
||||
//less effective when in a bad condition
|
||||
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / 100.0f);
|
||||
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
item.Submarine.ApplyForce(currForce);
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
string displayName = element.GetAttributeString("displayname", "");
|
||||
DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get(displayName);
|
||||
DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get($"DisplayName.{displayName}");
|
||||
|
||||
RequiredSkills = new List<Skill>();
|
||||
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
|
||||
@@ -212,10 +212,12 @@ namespace Barotrauma.Items.Components
|
||||
if (selectedItem == null) return;
|
||||
if (!outputContainer.Inventory.IsEmpty()) return;
|
||||
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
@@ -235,15 +237,17 @@ namespace Barotrauma.Items.Components
|
||||
outputContainer.Inventory.Locked = true;
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
}
|
||||
|
||||
private void CancelFabricating(Character user = null)
|
||||
{
|
||||
#if SERVER
|
||||
if (fabricatedItem != null && user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
IsActive = false;
|
||||
fabricatedItem = null;
|
||||
@@ -315,7 +319,12 @@ namespace Barotrauma.Items.Components
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
|
||||
}
|
||||
|
||||
if (GameMain.Client == null && user != null)
|
||||
bool isNotClient = true;
|
||||
#if CLIENT
|
||||
isNotClient = GameMain.Client == null;
|
||||
#endif
|
||||
|
||||
if (isNotClient && user != null)
|
||||
{
|
||||
foreach (Skill skill in fabricatedItem.RequiredSkills)
|
||||
{
|
||||
@@ -426,38 +435,5 @@ namespace Barotrauma.Items.Components
|
||||
item.prefab == requiredItem.ItemPrefab &&
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
if (itemIndex == -1)
|
||||
{
|
||||
CancelFabricating(c.Character);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if already fabricating the selected item, return
|
||||
if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return;
|
||||
if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return;
|
||||
#if CLIENT
|
||||
SelectItem(c.Character, fabricableItems[itemIndex]);
|
||||
#endif
|
||||
StartFabricating(fabricableItems[itemIndex], c.Character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
|
||||
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
|
||||
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
|
||||
msg.Write(userID);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
hasPower = voltage > minVoltage;
|
||||
if (hasPower)
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Barotrauma.Items.Components
|
||||
CurrFlow = 0.0f;
|
||||
currPowerConsumption = powerConsumption;
|
||||
//consume more power when in a bad condition
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
if (powerConsumption <= 0.0f)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount * 100.0f;
|
||||
//less effective when in bad condition
|
||||
CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
|
||||
CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
UpdateVents(CurrFlow);
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
|
||||
//pumps consume more power when in a bad condition
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
|
||||
//less effective when in a bad condition
|
||||
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
|
||||
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.Prefab.Health);
|
||||
|
||||
item.CurrentHull.WaterVolume += currFlow;
|
||||
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
|
||||
@@ -124,55 +124,29 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return false;
|
||||
#endif
|
||||
|
||||
if (objective.Option.ToLowerInvariant() == "stoppumping")
|
||||
{
|
||||
#if SERVER
|
||||
if (FlowPercentage > 0.0f) item.CreateServerEvent(this);
|
||||
#endif
|
||||
FlowPercentage = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if SERVER
|
||||
if (!IsActive || FlowPercentage > -100.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
IsActive = true;
|
||||
FlowPercentage = -100.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
|
||||
{
|
||||
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||
bool newIsActive = msg.ReadBoolean();
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
if (newFlowPercentage != FlowPercentage)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " set the pumping speed of " + item.Name + " to " + (int)(newFlowPercentage) + " %", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
if (newIsActive != IsActive)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + (newIsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
FlowPercentage = newFlowPercentage;
|
||||
IsActive = newIsActive;
|
||||
}
|
||||
|
||||
//notify all clients of the changed state
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
|
||||
msg.WriteRangedInteger(-10, 10, (int)(flowPercentage / 10.0f));
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,7 @@ namespace Barotrauma.Items.Components
|
||||
//(adjusts the fission rate and turbine output automatically to keep the
|
||||
//amount of power generated balanced with the load)
|
||||
private bool autoTemp;
|
||||
|
||||
private Client BlameOnBroken;
|
||||
|
||||
|
||||
//automatical adjustment to the power output when
|
||||
//turbine output and temperature are in the optimal range
|
||||
private float autoAdjustAmount;
|
||||
@@ -44,10 +42,7 @@ namespace Barotrauma.Items.Components
|
||||
private float sendUpdateTimer;
|
||||
|
||||
private float degreeOfSuccess;
|
||||
|
||||
private float? nextServerLogWriteTime;
|
||||
private float lastServerLogWriteTime;
|
||||
|
||||
|
||||
private Vector2 optimalTemperature, allowedTemperature;
|
||||
private Vector2 optimalFissionRate, allowedFissionRate;
|
||||
private Vector2 optimalTurbineOutput, allowedTurbineOutput;
|
||||
@@ -174,6 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && nextServerLogWriteTime != null)
|
||||
{
|
||||
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
|
||||
@@ -189,6 +185,7 @@ namespace Barotrauma.Items.Components
|
||||
lastServerLogWriteTime = (float)Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
prevAvailableFuel = AvailableFuel;
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
@@ -260,8 +257,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!connection.IsPower) continue;
|
||||
foreach (Connection recipient in connection.Recipients)
|
||||
{
|
||||
Item it = recipient.Item as Item;
|
||||
if (it == null) continue;
|
||||
if (!(recipient.Item is Item it)) continue;
|
||||
|
||||
PowerTransfer pt = it.GetComponent<PowerTransfer>();
|
||||
if (pt == null) continue;
|
||||
@@ -300,12 +296,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (unsentChanges && sendUpdateTimer <= 0.0f)
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
#if CLIENT
|
||||
else if (GameMain.Client != null)
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
@@ -321,7 +319,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.SendSignal(0, "1", "meltdown_warning", null);
|
||||
//faster meltdown if the item is in a bad condition
|
||||
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
|
||||
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.Prefab.Health);
|
||||
|
||||
if (meltDownTimer > MeltdownDelay)
|
||||
{
|
||||
@@ -338,7 +336,7 @@ namespace Barotrauma.Items.Components
|
||||
if (temperature > optimalTemperature.Y)
|
||||
{
|
||||
float prevFireTimer = fireTimer;
|
||||
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
|
||||
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.Prefab.Health);
|
||||
|
||||
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
|
||||
{
|
||||
@@ -388,9 +386,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void MeltDown()
|
||||
{
|
||||
if (item.Condition <= 0.0f || GameMain.Client != null) return;
|
||||
if (item.Condition <= 0.0f) return;
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return;
|
||||
#endif
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
|
||||
item.Condition = 0.0f;
|
||||
fireTimer = 0.0f;
|
||||
@@ -405,11 +408,13 @@ namespace Barotrauma.Items.Components
|
||||
containedItem.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Server != null && GameMain.Server.ConnectedClients.Contains(BlameOnBroken))
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && GameMain.Server.ConnectedClients.Contains(blameOnBroken))
|
||||
{
|
||||
BlameOnBroken.Karma = 0.0f;
|
||||
}
|
||||
blameOnBroken.Karma = 0.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
@@ -419,7 +424,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.Client != null) return false;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
|
||||
@@ -532,49 +537,5 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
bool autoTemp = msg.ReadBoolean();
|
||||
bool shutDown = msg.ReadBoolean();
|
||||
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
float turbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
if (!autoTemp && AutoTemp) BlameOnBroken = c;
|
||||
if (turbineOutput < targetTurbineOutput) BlameOnBroken = c;
|
||||
if (fissionRate > targetFissionRate) BlameOnBroken = c;
|
||||
if (!this.shutDown && shutDown) BlameOnBroken = c;
|
||||
|
||||
AutoTemp = autoTemp;
|
||||
this.shutDown = shutDown;
|
||||
targetFissionRate = fissionRate;
|
||||
targetTurbineOutput = turbineOutput;
|
||||
|
||||
LastUser = c.Character;
|
||||
if (nextServerLogWriteTime == null)
|
||||
{
|
||||
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
fissionRateScrollBar.BarScroll = 1.0f - targetFissionRate / 100.0f;
|
||||
turbineOutputScrollBar.BarScroll = 1.0f - targetTurbineOutput / 100.0f;
|
||||
onOffSwitch.BarScroll = shutDown ? Math.Max(onOffSwitch.BarScroll, 0.55f) : Math.Min(onOffSwitch.BarScroll, 0.45f);
|
||||
#endif
|
||||
|
||||
//need to create a server event to notify all clients of the changed state
|
||||
unsentChanges = true;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(autoTemp);
|
||||
msg.Write(shutDown);
|
||||
msg.WriteRangedSingle(temperature, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(degreeOfSuccess, 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +313,8 @@ namespace Barotrauma.Items.Components
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
IsActive = isActive;
|
||||
|
||||
//TODO: cleanup
|
||||
#if CLIENT
|
||||
activeTickBox.Selected = IsActive;
|
||||
#endif
|
||||
@@ -331,8 +333,9 @@ namespace Barotrauma.Items.Components
|
||||
directionalSlider.BarScroll = pingDirectionT;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
|
||||
@@ -187,10 +187,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
networkUpdateTimer = 0.1f;
|
||||
unsentChanges = false;
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Barotrauma.Items.Components
|
||||
//how fast it's currently being recharged (can be changed, so that
|
||||
//charging can be slowed down or disabled if there's a shortage of power)
|
||||
private float rechargeSpeed;
|
||||
|
||||
private float maxOutput;
|
||||
|
||||
private float lastSentCharge;
|
||||
|
||||
//charge indicator description
|
||||
@@ -32,6 +29,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected bool isHorizontal;
|
||||
|
||||
//a list of powered devices connected directly to this item
|
||||
private readonly List<Pair<Powered, Connection>> directlyConnected = new List<Pair<Powered, Connection>>(10);
|
||||
|
||||
//charge indicator description
|
||||
protected Vector2 indicatorPosition, indicatorSize;
|
||||
|
||||
protected bool isHorizontal;
|
||||
|
||||
public float CurrPowerOutput
|
||||
{
|
||||
get;
|
||||
@@ -60,11 +65,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Maximum output of the device when fully charged (kW)."), Serialize(10.0f, true)]
|
||||
public float MaxOutPut
|
||||
{
|
||||
set { maxOutput = value; }
|
||||
get { return maxOutput; }
|
||||
}
|
||||
public float MaxOutPut { set; get; }
|
||||
|
||||
[Serialize(10.0f, true), Editable(ToolTip = "The maximum capacity of the device (kW * min). "+
|
||||
"For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
|
||||
@@ -86,7 +87,9 @@ namespace Barotrauma.Items.Components
|
||||
//send a network event if the charge has changed by more than 5%
|
||||
if (Math.Abs(charge - lastSentCharge) / capacity > 0.05f)
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null) item.CreateServerEvent(this);
|
||||
#endif
|
||||
lastSentCharge = charge;
|
||||
}
|
||||
}
|
||||
@@ -129,11 +132,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
float chargeRatio = (float)(Math.Sqrt(charge / capacity));
|
||||
float chargeRatio = charge / capacity;
|
||||
float gridPower = 0.0f;
|
||||
float gridLoad = 0.0f;
|
||||
directlyConnected.Clear();
|
||||
|
||||
List<Pair<Powered, Connection>> directlyConnected = new List<Pair<Powered, Connection>>();
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
if (c.Name == "power_in") continue;
|
||||
@@ -187,9 +190,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (gridPower < gridLoad)
|
||||
{
|
||||
//output starts dropping when the charge is less than 10%
|
||||
float maxOutputRatio = 1.0f;
|
||||
if (chargeRatio < 0.1f)
|
||||
{
|
||||
maxOutputRatio = Math.Max(chargeRatio * 10.0f, 0.0f);
|
||||
}
|
||||
|
||||
CurrPowerOutput = MathHelper.Lerp(
|
||||
CurrPowerOutput,
|
||||
Math.Min(maxOutput * chargeRatio, gridLoad),
|
||||
Math.Min(MaxOutPut * maxOutputRatio, gridLoad),
|
||||
deltaTime * 10.0f);
|
||||
}
|
||||
else
|
||||
@@ -212,13 +222,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return false;
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
|
||||
{
|
||||
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * 0.5f) > 0.05f)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
RechargeSpeed = maxRechargeSpeed * 0.5f;
|
||||
#if CLIENT
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
@@ -232,7 +246,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (rechargeSpeed > 0.0f)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
RechargeSpeed = 0.0f;
|
||||
#if CLIENT
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
@@ -259,26 +275,5 @@ namespace Barotrauma.Items.Components
|
||||
outputVoltage = power;
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
float newRechargeSpeed = msg.ReadRangedInteger(0, 10) / 10.0f * maxRechargeSpeed;
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
RechargeSpeed = newRechargeSpeed;
|
||||
GameServer.Log(c.Character.LogName + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
|
||||
|
||||
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
|
||||
msg.WriteRangedSingle(chargeRatio, 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +169,14 @@ namespace Barotrauma.Items.Components
|
||||
pt.Item.SendSignal(0, "", "power", null, voltage);
|
||||
pt.Item.SendSignal(0, "", "power_out", null, voltage);
|
||||
|
||||
#if CLIENT
|
||||
//damage the item if voltage is too high
|
||||
//(except if running as a client)
|
||||
if (GameMain.Client != null) continue;
|
||||
#endif
|
||||
|
||||
//items in a bad condition are more sensitive to overvoltage
|
||||
float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / item.Prefab.Health);
|
||||
|
||||
//items in a bad condition are more sensitive to overvoltage
|
||||
float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / 100.0f);
|
||||
@@ -313,26 +318,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
float maxPower = this is RelayComponent relayComponent ? relayComponent.MaxPower : float.PositiveInfinity;
|
||||
|
||||
foreach (Connection c in PowerConnections)
|
||||
{
|
||||
var recipients = c.Recipients;
|
||||
foreach (Connection recipient in recipients)
|
||||
{
|
||||
if (recipient == null) continue;
|
||||
if (recipient?.Item == null) continue;
|
||||
|
||||
Item it = recipient.Item;
|
||||
if (it == null) continue;
|
||||
|
||||
if (it.Condition <= 0.0f) continue;
|
||||
|
||||
foreach (ItemComponent ic in it.components)
|
||||
foreach (ItemComponent ic in it.Components)
|
||||
{
|
||||
Powered powered = ic as Powered;
|
||||
if (powered == null || !powered.IsActive) continue;
|
||||
if (!(ic is Powered powered) || !powered.IsActive) continue;
|
||||
if (connectedList.Contains(powered)) continue;
|
||||
|
||||
PowerTransfer powerTransfer = powered as PowerTransfer;
|
||||
if (powerTransfer != null)
|
||||
if (powered is PowerTransfer powerTransfer)
|
||||
{
|
||||
if (this is RelayComponent == powerTransfer is RelayComponent)
|
||||
{
|
||||
@@ -348,8 +351,7 @@ namespace Barotrauma.Items.Components
|
||||
continue;
|
||||
}
|
||||
|
||||
PowerContainer powerContainer = powered as PowerContainer;
|
||||
if (powerContainer != null)
|
||||
if (powered is PowerContainer powerContainer)
|
||||
{
|
||||
if (recipient.Name == "power_in")
|
||||
{
|
||||
@@ -357,7 +359,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
fullPower += powerContainer.CurrPowerOutput;
|
||||
fullPower += Math.Min(powerContainer.CurrPowerOutput, maxPower);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -372,7 +374,7 @@ namespace Barotrauma.Items.Components
|
||||
//negative power consumption = the construction is a
|
||||
//generator/battery or another junction box
|
||||
{
|
||||
fullPower -= powered.CurrPowerConsumption;
|
||||
fullPower -= Math.Max(powered.CurrPowerConsumption, -maxPower);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,7 +424,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (recipient.Item == item || recipient.Item == source) continue;
|
||||
|
||||
foreach (ItemComponent ic in recipient.Item.components)
|
||||
foreach (ItemComponent ic in recipient.Item.Components)
|
||||
{
|
||||
//powertransfer components don't need to receive the signal in the pass-through signal connections
|
||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||
|
||||
@@ -12,9 +12,7 @@ namespace Barotrauma.Items.Components
|
||||
public static float SkillIncreaseMultiplier = 0.4f;
|
||||
|
||||
private string header;
|
||||
|
||||
private float lastSentProgress;
|
||||
|
||||
|
||||
private float fixDurationLowSkill, fixDurationHighSkill;
|
||||
|
||||
private float deteriorationTimer;
|
||||
@@ -71,7 +69,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return currentFixer; }
|
||||
set
|
||||
{
|
||||
if (currentFixer == value || item.Condition >= 100.0f) return;
|
||||
if (currentFixer == value || item.Condition >= item.Prefab.Health) return;
|
||||
if (currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None;
|
||||
currentFixer = value;
|
||||
}
|
||||
@@ -93,8 +91,11 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
|
||||
|
||||
#if SERVER
|
||||
//let the clients know the initial deterioration delay
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
@@ -119,10 +120,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (deteriorationTimer > 0.0f)
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
deteriorationTimer -= deltaTime;
|
||||
#if SERVER
|
||||
if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +147,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
UpdateFixAnimation(CurrentFixer);
|
||||
|
||||
if (GameMain.Client != null) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
float successFactor = requiredSkills.Count == 0 ? 1.0f : 0.0f;
|
||||
foreach (Skill skill in requiredSkills)
|
||||
@@ -166,10 +169,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.Condition += deltaTime / (fixDuration / item.Prefab.Health);
|
||||
}
|
||||
|
||||
|
||||
if (wasBroken && item.Condition >= item.Prefab.Health)
|
||||
{
|
||||
SteamAchievementManager.OnItemRepaired(item, currentFixer);
|
||||
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Barotrauma.Items.Components
|
||||
source.LastSentSignalRecipients.Add(recipient.item);
|
||||
}
|
||||
|
||||
foreach (ItemComponent ic in recipient.item.components)
|
||||
foreach (ItemComponent ic in recipient.item.Components)
|
||||
{
|
||||
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, power, signalStrength);
|
||||
}
|
||||
|
||||
@@ -220,144 +220,5 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
List<Wire>[] wires = new List<Wire>[Connections.Count];
|
||||
|
||||
//read wire IDs for each connection
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
wires[i] = new List<Wire>();
|
||||
for (int j = 0; j < Connection.MaxLinked; j++)
|
||||
{
|
||||
ushort wireId = msg.ReadUInt16();
|
||||
|
||||
Item wireItem = Entity.FindEntityByID(wireId) as Item;
|
||||
if (wireItem == null) continue;
|
||||
|
||||
Wire wireComponent = wireItem.GetComponent<Wire>();
|
||||
if (wireComponent != null)
|
||||
{
|
||||
wires[i].Add(wireComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//don't allow rewiring locked panels
|
||||
if (Locked) return;
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
|
||||
//check if the character can access this connectionpanel
|
||||
//and all the wires they're trying to connect
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
foreach (Wire wire in wires[i])
|
||||
{
|
||||
//wire not found in any of the connections yet (client is trying to connect a new wire)
|
||||
// -> we need to check if the client has access to it
|
||||
if (!Connections.Any(connection => connection.Wires.Contains(wire)))
|
||||
{
|
||||
if (!wire.Item.CanClientAccess(c)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go through existing wire links
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
int j = -1;
|
||||
foreach (Wire existingWire in Connections[i].Wires)
|
||||
{
|
||||
j++;
|
||||
if (existingWire == null) continue;
|
||||
|
||||
//existing wire not in the list of new wires -> disconnect it
|
||||
if (!wires[i].Contains(existingWire))
|
||||
{
|
||||
if (existingWire.Locked)
|
||||
{
|
||||
//this should not be possible unless the client is running a modified version of the game
|
||||
GameServer.Log(c.Character.LogName + " attempted to disconnect a locked wire from " +
|
||||
Connections[i].Item.Name + " (" + Connections[i].Name + ")", ServerLog.MessageType.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
existingWire.RemoveConnection(item);
|
||||
|
||||
if (existingWire.Connections[0] == null && existingWire.Connections[1] == null)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " disconnected a wire from " +
|
||||
Connections[i].Item.Name + " (" + Connections[i].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else if (existingWire.Connections[0] != null)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " disconnected a wire from " +
|
||||
Connections[i].Item.Name + " (" + Connections[i].Name + ") to " + existingWire.Connections[0].Item.Name + " (" + existingWire.Connections[0].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
//wires that are not in anyone's inventory (i.e. not currently being rewired)
|
||||
//can never be connected to only one connection
|
||||
// -> the client must have dropped the wire from the connection panel
|
||||
if (existingWire.Item.ParentInventory == null && !wires.Any(w => w.Contains(existingWire)))
|
||||
{
|
||||
//let other clients know the item was also disconnected from the other connection
|
||||
existingWire.Connections[0].Item.CreateServerEvent(existingWire.Connections[0].Item.GetComponent<ConnectionPanel>());
|
||||
existingWire.Item.Drop(c.Character);
|
||||
}
|
||||
}
|
||||
else if (existingWire.Connections[1] != null)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " disconnected a wire from " +
|
||||
Connections[i].Item.Name + " (" + Connections[i].Name + ") to " + existingWire.Connections[1].Item.Name + " (" + existingWire.Connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
if (existingWire.Item.ParentInventory == null && !wires.Any(w => w.Contains(existingWire)))
|
||||
{
|
||||
//let other clients know the item was also disconnected from the other connection
|
||||
existingWire.Connections[1].Item.CreateServerEvent(existingWire.Connections[1].Item.GetComponent<ConnectionPanel>());
|
||||
existingWire.Item.Drop(c.Character);
|
||||
}
|
||||
}
|
||||
|
||||
Connections[i].SetWire(j, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go through new wires
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
foreach (Wire newWire in wires[i])
|
||||
{
|
||||
//already connected, no need to do anything
|
||||
if (Connections[i].Wires.Contains(newWire)) continue;
|
||||
|
||||
Connections[i].TryAddLink(newWire);
|
||||
newWire.Connect(Connections[i], true, true);
|
||||
|
||||
var otherConnection = newWire.OtherConnection(Connections[i]);
|
||||
|
||||
if (otherConnection == null)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " connected a wire to " +
|
||||
Connections[i].Item.Name + " (" + Connections[i].Name + ")",
|
||||
ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " connected a wire from " +
|
||||
Connections[i].Item.Name + " (" + Connections[i].Name + ") to " +
|
||||
(otherConnection == null ? "none" : otherConnection.Item.Name + " (" + (otherConnection.Name) + ")"),
|
||||
ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
ClientWrite(msg, extraData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,57 +137,5 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(0, ciElement.State ? ciElement.Signal : "0", ciElement.Connection, sender: null, source: item);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
bool[] elementStates = new bool[customInterfaceElementList.Count];
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
elementStates[i] = msg.ReadBoolean();
|
||||
}
|
||||
|
||||
CustomInterfaceElement clickedButton = null;
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
if (customInterfaceElementList[i].ContinuousSignal)
|
||||
{
|
||||
TickBoxToggled(customInterfaceElementList[i], elementStates[i]);
|
||||
}
|
||||
else if (elementStates[i])
|
||||
{
|
||||
clickedButton = customInterfaceElementList[i];
|
||||
ButtonClicked(customInterfaceElementList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//notify all clients of the new state
|
||||
GameMain.Server.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ComponentState,
|
||||
item.components.IndexOf(this),
|
||||
clickedButton
|
||||
});
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
//extradata contains an array of buttons clicked by a client (or nothing if nothing was clicked)
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
if (customInterfaceElementList[i].ContinuousSignal)
|
||||
{
|
||||
msg.Write(customInterfaceElementList[i].State);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(extraData != null && extraData.Any(d => d as CustomInterfaceElement == customInterfaceElementList[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,9 @@ namespace Barotrauma.Items.Components
|
||||
if (IsActive == value) return;
|
||||
|
||||
IsActive = value;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null) item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +154,7 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
|
||||
IsActive = IsOn;
|
||||
item.AddTag("light");
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -174,7 +177,7 @@ namespace Barotrauma.Items.Components
|
||||
if (body != null)
|
||||
{
|
||||
#if CLIENT
|
||||
light.Rotation = body.Dir > 0.0f ? body.Rotation : body.Rotation - MathHelper.Pi;
|
||||
light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
|
||||
light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
#endif
|
||||
if (!body.Enabled)
|
||||
@@ -234,9 +237,16 @@ namespace Barotrauma.Items.Components
|
||||
light.Color = lightColor * lightBrightness * (1.0f - Rand.Range(0.0f, Flicker));
|
||||
light.Range = range;
|
||||
#endif
|
||||
item.SightRange = Math.Max(range * (float)Math.Sqrt(lightBrightness), item.SightRange);
|
||||
}
|
||||
|
||||
if (AITarget != null)
|
||||
{
|
||||
UpdateAITarget(AITarget);
|
||||
}
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
UpdateAITarget(item.AiTarget);
|
||||
}
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
@@ -279,5 +289,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
msg.Write(IsOn);
|
||||
}
|
||||
|
||||
private void UpdateAITarget(AITarget target)
|
||||
{
|
||||
//voltage > minVoltage || powerConsumption <= 0.0f; <- ?
|
||||
target.Enabled = IsActive;
|
||||
if (target.MaxSightRange <= 0)
|
||||
{
|
||||
target.MaxSightRange = Range * 5;
|
||||
}
|
||||
target.SightRange = IsActive ? target.MaxSightRange * lightBrightness : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,13 @@ namespace Barotrauma.Items.Components
|
||||
set { falseOutput = value; }
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "How fast the objects within the detector's range have to be moving (in m/s).", DecimalCount = 3), Serialize(0.01f, true)]
|
||||
public float MinimumVelocity
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public MotionSensor(Item item, XElement element)
|
||||
: base (item, element)
|
||||
@@ -106,7 +113,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.body != null && item.body.Enabled)
|
||||
{
|
||||
if (Math.Abs(item.body.LinearVelocity.X) > 0.01f || Math.Abs(item.body.LinearVelocity.Y) > 0.1f)
|
||||
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
|
||||
{
|
||||
motionDetected = true;
|
||||
}
|
||||
@@ -130,7 +137,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.LinearVelocity.LengthSquared() <= 0.001f) continue;
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) continue;
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
{
|
||||
motionDetected = true;
|
||||
|
||||
@@ -51,7 +51,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.SendSignal(0, IsOn ? "1" : "0", "state_out", null);
|
||||
|
||||
if (Math.Min(-currPowerConsumption, PowerLoad) > maxPower) item.Condition = 0.0f;
|
||||
if (Math.Min(-currPowerConsumption, PowerLoad) > maxPower && CanBeOverloaded)
|
||||
{
|
||||
item.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
@@ -81,12 +84,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void SetState(bool on, bool isNetworkMessage)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !isNetworkMessage) return;
|
||||
#endif
|
||||
|
||||
#if SERVER
|
||||
if (on != IsOn && GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
IsOn = on;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private string output, falseOutput;
|
||||
|
||||
//how often the detector can switch from state to another
|
||||
const float StateSwitchInterval = 1.0f;
|
||||
|
||||
private bool isInWater;
|
||||
private float stateSwitchDelay;
|
||||
|
||||
[InGameEditable, Serialize("1", true)]
|
||||
public string Output
|
||||
{
|
||||
@@ -28,22 +34,37 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
string signalOut = falseOutput;
|
||||
if (item.InWater)
|
||||
if (stateSwitchDelay > 0.0f)
|
||||
{
|
||||
//item in water -> we definitely want to send the True output
|
||||
signalOut = Output;
|
||||
stateSwitchDelay -= deltaTime;
|
||||
}
|
||||
else if (item.CurrentHull != null)
|
||||
else
|
||||
{
|
||||
//item in not water -> check if there's water anywhere within the rect of the item
|
||||
if (item.CurrentHull.Surface > item.CurrentHull.Rect.Y - item.CurrentHull.Rect.Height + 1 &&
|
||||
item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
|
||||
bool prevState = isInWater;
|
||||
|
||||
isInWater = false;
|
||||
if (item.InWater)
|
||||
{
|
||||
signalOut = output;
|
||||
//item in water -> we definitely want to send the True output
|
||||
isInWater = true;
|
||||
}
|
||||
else if (item.CurrentHull != null)
|
||||
{
|
||||
//item in not water -> check if there's water anywhere within the rect of the item
|
||||
if (item.CurrentHull.Surface > item.CurrentHull.Rect.Y - item.CurrentHull.Rect.Height + 1 &&
|
||||
item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
|
||||
{
|
||||
isInWater = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevState != isInWater)
|
||||
{
|
||||
stateSwitchDelay = StateSwitchInterval;
|
||||
}
|
||||
}
|
||||
|
||||
string signalOut = isInWater ? output : falseOutput;
|
||||
if (!string.IsNullOrEmpty(signalOut))
|
||||
{
|
||||
item.SendSignal(0, signalOut, "signal_out", null);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private string prevSignal;
|
||||
|
||||
public byte TeamID;
|
||||
public Character.TeamType TeamID;
|
||||
|
||||
[Serialize(20000.0f, false)]
|
||||
public float Range
|
||||
@@ -134,12 +134,16 @@ namespace Barotrauma.Items.Components
|
||||
if (chatMsg.Length > ChatMessage.MaxLength) chatMsg = chatMsg.Substring(0, ChatMessage.MaxLength);
|
||||
if (string.IsNullOrEmpty(chatMsg)) continue;
|
||||
|
||||
#if CLIENT
|
||||
if (wifiComp.item.ParentInventory.Owner == Character.Controlled)
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
GameMain.NetworkMember.AddChatMessage(signal, ChatMessageType.Radio, source == null ? "" : source.Name);
|
||||
}
|
||||
else if (GameMain.Server != null)
|
||||
#endif
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Client recipientClient = GameMain.Server.ConnectedClients.Find(c => c.Character == wifiComp.item.ParentInventory.Owner);
|
||||
if (recipientClient != null)
|
||||
@@ -148,6 +152,7 @@ namespace Barotrauma.Items.Components
|
||||
ChatMessage.Create(source == null ? "" : source.Name, chatMsg, ChatMessageType.Radio, null), recipientClient);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
chatMsgSent = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,24 @@ namespace Barotrauma.Items.Components
|
||||
partial class WireSection
|
||||
{
|
||||
private Vector2 start;
|
||||
private Vector2 end;
|
||||
|
||||
private float angle;
|
||||
private float length;
|
||||
|
||||
public Vector2 Start
|
||||
{
|
||||
get { return start; }
|
||||
}
|
||||
public Vector2 End
|
||||
{
|
||||
get { return end; }
|
||||
}
|
||||
|
||||
public WireSection(Vector2 start, Vector2 end)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
|
||||
angle = MathUtils.VectorToAngle(end - start);
|
||||
length = Vector2.Distance(start, end);
|
||||
@@ -41,6 +52,8 @@ namespace Barotrauma.Items.Components
|
||||
private bool canPlaceNode;
|
||||
private Vector2 newNodePos;
|
||||
|
||||
private Vector2 sectionExtents;
|
||||
|
||||
public bool Hidden;
|
||||
|
||||
private bool locked;
|
||||
@@ -185,7 +198,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (connections[0] != null && connections[1] != null)
|
||||
{
|
||||
foreach (ItemComponent ic in item.components)
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic == this) continue;
|
||||
ic.Drop(null);
|
||||
@@ -202,10 +215,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (sendNetworkEvent)
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CreateNetworkEvent();
|
||||
}
|
||||
#endif
|
||||
//the wire is active if only one end has been connected
|
||||
IsActive = connections[0] == null ^ connections[1] == null;
|
||||
}
|
||||
@@ -274,8 +289,7 @@ namespace Barotrauma.Items.Components
|
||||
//prevent the wire from extending too far when rewiring
|
||||
if (nodes.Count > 0)
|
||||
{
|
||||
Character user = item.ParentInventory?.Owner as Character;
|
||||
if (user == null) return;
|
||||
if (!(item.ParentInventory?.Owner is Character user)) return;
|
||||
|
||||
Vector2 prevNodePos = nodes[nodes.Count - 1];
|
||||
if (sub != null) { prevNodePos += sub.HiddenSubPosition; }
|
||||
@@ -294,11 +308,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f);
|
||||
user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * 200.0f);
|
||||
if (currLength > MaxLength * 1.5f && GameMain.Client == null)
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
ClearConnections();
|
||||
CreateNetworkEvent();
|
||||
return;
|
||||
if (currLength > MaxLength * 1.5f)
|
||||
{
|
||||
ClearConnections();
|
||||
#if SERVER
|
||||
CreateNetworkEvent();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,12 +328,18 @@ namespace Barotrauma.Items.Components
|
||||
newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition;
|
||||
canPlaceNode = true;
|
||||
}
|
||||
|
||||
sectionExtents = new Vector2(
|
||||
Math.Max(Math.Abs((newNodePos.X + sub.HiddenSubPosition.X) - item.Position.X), sectionExtents.X),
|
||||
Math.Max(Math.Abs((newNodePos.Y + sub.HiddenSubPosition.Y) - item.Position.Y), sectionExtents.Y));
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null) return false;
|
||||
#if CLIENT
|
||||
if (character == Character.Controlled && character.SelectedConstruction != null) return false;
|
||||
#endif
|
||||
|
||||
if (newNodePos != Vector2.Zero && canPlaceNode && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance)
|
||||
{
|
||||
@@ -328,10 +354,12 @@ namespace Barotrauma.Items.Components
|
||||
Drawable = true;
|
||||
newNodePos = Vector2.Zero;
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CreateNetworkEvent();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -393,18 +421,33 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
sections.Clear();
|
||||
|
||||
for (int i = 0; i < nodes.Count-1; i++)
|
||||
for (int i = 0; i < nodes.Count - 1; i++)
|
||||
{
|
||||
sections.Add(new WireSection(nodes[i], nodes[i + 1]));
|
||||
}
|
||||
Drawable = IsActive || sections.Count > 0;
|
||||
CalculateExtents();
|
||||
}
|
||||
|
||||
private void CalculateExtents()
|
||||
{
|
||||
sectionExtents = Vector2.Zero;
|
||||
if (sections.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
sectionExtents.X = Math.Max(Math.Abs(nodes[i].X - item.Position.X), sectionExtents.X);
|
||||
sectionExtents.Y = Math.Max(Math.Abs(nodes[i].Y - item.Position.Y), sectionExtents.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearConnections(Character user = null)
|
||||
{
|
||||
nodes.Clear();
|
||||
sections.Clear();
|
||||
|
||||
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
if (connections[0] != null && connections[1] != null)
|
||||
@@ -424,6 +467,7 @@ namespace Barotrauma.Items.Components
|
||||
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SetConnectedDirty();
|
||||
|
||||
@@ -627,33 +671,6 @@ namespace Barotrauma.Items.Components
|
||||
base.RemoveComponentSpecific();
|
||||
}
|
||||
|
||||
private void CreateNetworkEvent()
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
//split into multiple events because one might not be enough to fit all the nodes
|
||||
int eventCount = Math.Max((int)Math.Ceiling(nodes.Count / (float)MaxNodesPerNetworkEvent), 1);
|
||||
for (int i = 0; i < eventCount; i++)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.components.IndexOf(this), i });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
int eventIndex = (int)extraData[2];
|
||||
int nodeStartIndex = eventIndex * MaxNodesPerNetworkEvent;
|
||||
int nodeCount = MathHelper.Clamp(nodes.Count - nodeStartIndex, 0, MaxNodesPerNetworkEvent);
|
||||
|
||||
msg.WriteRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent), eventIndex);
|
||||
msg.WriteRangedInteger(0, MaxNodesPerNetworkEvent, nodeCount);
|
||||
for (int i = nodeStartIndex; i < nodeStartIndex + nodeCount; i++)
|
||||
{
|
||||
msg.Write(nodes[i].X);
|
||||
msg.Write(nodes[i].Y);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
||||
{
|
||||
int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent));
|
||||
|
||||
@@ -189,6 +189,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Parent = null;
|
||||
lightComponent.Rotation = rotation;
|
||||
lightComponent.Light.Rotation = -rotation;
|
||||
}
|
||||
@@ -258,7 +259,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool TryLaunch(float deltaTime, Character character = null)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return false;
|
||||
#endif
|
||||
|
||||
if (reload > 0.0f) return false;
|
||||
|
||||
@@ -313,27 +316,32 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
battery.Charge -= takePower / 3600.0f;
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
battery.Item.CreateServerEvent(battery);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Launch(projectiles[0].Item, character);
|
||||
|
||||
#if SERVER
|
||||
if (character != null)
|
||||
{
|
||||
string msg = character.LogName + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name;
|
||||
if (projectiles[0].Item.ContainedItems == null || projectiles[0].Item.ContainedItems.All(i => i == null))
|
||||
var containedItems = projectiles[0].Item.ContainedItems;
|
||||
if (containedItems == null || !containedItems.Any())
|
||||
{
|
||||
msg += ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += ", contained items: " + string.Join(", ", Array.FindAll(projectiles[0].Item.ContainedItems, i => i != null).Select(i => i.Name)) + ")";
|
||||
msg += ", contained items: " + string.Join(", ", containedItems.Select(i => i.Name)) + ")";
|
||||
}
|
||||
GameServer.Log(msg, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -359,10 +367,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
if (projectile.Container != null) projectile.Container.RemoveContained(projectile);
|
||||
|
||||
if (GameMain.Server != null)
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.components.IndexOf(this), projectile });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), projectile });
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
|
||||
@@ -415,18 +423,9 @@ namespace Barotrauma.Items.Components
|
||||
if (containedItems != null)
|
||||
{
|
||||
var container = projectileContainer.GetComponent<ItemContainer>();
|
||||
if (containedItems != null) maxProjectileCount += container.Capacity;
|
||||
|
||||
int projectiles = 0;
|
||||
|
||||
for (int i = 0; i < containedItems.Length; i++)
|
||||
{
|
||||
if (containedItems[i].Condition > 0.0f)
|
||||
{
|
||||
projectiles++;
|
||||
}
|
||||
}
|
||||
maxProjectileCount += container.Capacity;
|
||||
|
||||
int projectiles = containedItems.Count(it => it.Condition > 0.0f);
|
||||
usableProjectileCount += projectiles;
|
||||
}
|
||||
}
|
||||
@@ -465,14 +464,21 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//ignore humans and characters that are inside the sub
|
||||
if (enemy.IsDead || enemy.SpeciesName == "human" || enemy.AnimController.CurrentHull != null) continue;
|
||||
|
||||
if (enemy.IsDead|| enemy.AnimController.CurrentHull != null || !enemy.Enabled) { continue; }
|
||||
if (enemy.SpeciesName == character.SpeciesName && enemy.TeamID == character.TeamID) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestEnemy = enemy;
|
||||
closestDist = dist;
|
||||
}
|
||||
if (dist > closestDist) { continue; }
|
||||
|
||||
float angle = -MathUtils.VectorToAngle(enemy.WorldPosition - item.WorldPosition);
|
||||
float midRotation = (minRotation + maxRotation) / 2.0f;
|
||||
while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
|
||||
while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
|
||||
|
||||
if (angle < minRotation || angle > maxRotation) { continue; }
|
||||
|
||||
closestEnemy = enemy;
|
||||
closestDist = dist;
|
||||
}
|
||||
|
||||
if (closestEnemy == null) return false;
|
||||
@@ -486,7 +492,7 @@ namespace Barotrauma.Items.Components
|
||||
float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition);
|
||||
float turretAngle = -rotation;
|
||||
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.1f) return false;
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) return false;
|
||||
|
||||
var pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), closestEnemy.SimPosition, null);
|
||||
if (pickedBody != null && !(pickedBody.UserData is Limb)) return false;
|
||||
@@ -559,9 +565,9 @@ namespace Barotrauma.Items.Components
|
||||
var containedItems = projectileContainer.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
|
||||
for (int i = 0; i < containedItems.Length; i++)
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
var projectileComponent = containedItems[i].GetComponent<Projectile>();
|
||||
var projectileComponent = containedItem.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
{
|
||||
projectiles.Add(projectileComponent);
|
||||
@@ -570,10 +576,10 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//check if the contained item is another itemcontainer with projectiles inside it
|
||||
if (containedItems[i].ContainedItems == null) continue;
|
||||
for (int j = 0; j < containedItems[i].ContainedItems.Length; j++)
|
||||
if (containedItem.ContainedItems == null) continue;
|
||||
foreach (Item subContainedItem in containedItem.ContainedItems)
|
||||
{
|
||||
projectileComponent = containedItems[i].ContainedItems[j].GetComponent<Projectile>();
|
||||
projectileComponent = subContainedItem.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
{
|
||||
projectiles.Add(projectileComponent);
|
||||
@@ -653,12 +659,9 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
case "toggle":
|
||||
case "toggle_light":
|
||||
foreach (ItemComponent component in item.components)
|
||||
if (lightComponent != null)
|
||||
{
|
||||
if (component.Parent == this && component is LightComponent lightComponent)
|
||||
{
|
||||
lightComponent.IsOn = !lightComponent.IsOn;
|
||||
}
|
||||
lightComponent.IsOn = !lightComponent.IsOn;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -197,9 +197,11 @@ namespace Barotrauma.Items.Components
|
||||
foreach (XElement lightElement in subElement.Elements())
|
||||
{
|
||||
if (lightElement.Name.ToString().ToLowerInvariant() != "lightcomponent") continue;
|
||||
wearableSprites[i].LightComponent = new LightComponent(item, lightElement);
|
||||
wearableSprites[i].LightComponent.Parent = this;
|
||||
item.components.Add(wearableSprites[i].LightComponent);
|
||||
wearableSprites[i].LightComponent = new LightComponent(item, lightElement)
|
||||
{
|
||||
Parent = this
|
||||
};
|
||||
item.AddComponent(wearableSprites[i].LightComponent);
|
||||
}
|
||||
|
||||
i++;
|
||||
@@ -225,9 +227,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
|
||||
if (equipLimb == null) continue;
|
||||
if (equipLimb == null) { continue; }
|
||||
|
||||
item.body.Enabled = false;
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.Enabled = false;
|
||||
}
|
||||
IsActive = true;
|
||||
if (wearableSprite.LightComponent != null)
|
||||
{
|
||||
|
||||
@@ -19,9 +19,7 @@ namespace Barotrauma
|
||||
|
||||
public bool Locked;
|
||||
|
||||
private ushort[] receivedItemIDs;
|
||||
protected float syncItemsDelay;
|
||||
private CoroutineHandle syncItemsCoroutine;
|
||||
|
||||
public int Capacity
|
||||
{
|
||||
@@ -311,19 +309,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void CreateNetworkEvent()
|
||||
public virtual void CreateNetworkEvent()
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(Owner as IServerSerializable, new object[] { NetEntityEvent.Type.InventoryState });
|
||||
if (GameMain.NetworkMember.IsClient) { syncItemsDelay = 1.0f; }
|
||||
GameMain.NetworkMember.CreateEntityEvent(Owner as INetSerializable, new object[] { NetEntityEvent.Type.InventoryState });
|
||||
}
|
||||
#if CLIENT
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
syncItemsDelay = 1.0f;
|
||||
GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public Item FindItemByTag(string tag)
|
||||
@@ -364,6 +356,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SharedWrite(NetBuffer msg, object[] extraData = null)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all items inside the inventory (and also recursively all items inside the items)
|
||||
/// </summary>
|
||||
@@ -378,140 +378,6 @@ namespace Barotrauma
|
||||
}
|
||||
Items[i].Remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(NetBuffer msg, object[] extraData = null)
|
||||
{
|
||||
ServerWrite(msg, null);
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
List<Item> prevItems = new List<Item>(Items);
|
||||
ushort[] newItemIDs = new ushort[capacity];
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
newItemIDs[i] = msg.ReadUInt16();
|
||||
}
|
||||
|
||||
|
||||
if (c == null || c.Character == null) return;
|
||||
|
||||
bool accessible = c.Character.CanAccessInventory(this);
|
||||
if (this is CharacterInventory && accessible)
|
||||
{
|
||||
if (Owner == null || !(Owner is Character))
|
||||
{
|
||||
accessible = false;
|
||||
}
|
||||
else if (!((CharacterInventory)this).AccessibleWhenAlive && !((Character)Owner).IsDead)
|
||||
{
|
||||
accessible = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accessible)
|
||||
{
|
||||
//create a network event to correct the client's inventory state
|
||||
//otherwise they may have an item in their inventory they shouldn't have been able to pick up,
|
||||
//and receiving an event for that inventory later will cause the item to be dropped
|
||||
CreateNetworkEvent();
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
var item = Entity.FindEntityByID(newItemIDs[i]) as Item;
|
||||
if (item == null) continue;
|
||||
if (item.ParentInventory != null && item.ParentInventory != this)
|
||||
{
|
||||
item.ParentInventory.CreateNetworkEvent();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<Inventory> prevItemInventories = new List<Inventory>(Items.Select(i => i?.ParentInventory));
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
Item newItem = newItemIDs[i] == 0 ? null : Entity.FindEntityByID(newItemIDs[i]) as Item;
|
||||
prevItemInventories.Add(newItem?.ParentInventory);
|
||||
|
||||
if (newItemIDs[i] == 0 || (newItem != Items[i]))
|
||||
{
|
||||
if (Items[i] != null) Items[i].Drop();
|
||||
System.Diagnostics.Debug.Assert(Items[i] == null);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (newItemIDs[i] > 0)
|
||||
{
|
||||
var item = Entity.FindEntityByID(newItemIDs[i]) as Item;
|
||||
if (item == null || item == Items[i]) continue;
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
var holdable = item.GetComponent<Holdable>();
|
||||
if (holdable != null && !holdable.CanBeDeattached()) continue;
|
||||
|
||||
if (!item.CanClientAccess(c)) continue;
|
||||
}
|
||||
TryPutItem(item, i, true, true, c.Character, false);
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (Items[j] == item && newItemIDs[j] != item.ID)
|
||||
{
|
||||
Items[j] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreateNetworkEvent();
|
||||
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
||||
{
|
||||
if (prevInventory != this) prevInventory?.CreateNetworkEvent();
|
||||
}
|
||||
|
||||
foreach (Item item in Items.Distinct())
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (!prevItems.Contains(item))
|
||||
{
|
||||
if (Owner == c.Character)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName+ " picked up " + item.Name, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " placed " + item.Name + " in " + Owner, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item item in prevItems.Distinct())
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (!Items.Contains(item))
|
||||
{
|
||||
if (Owner == c.Character)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " dropped " + item.Name, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " removed " + item.Name + " from " + Owner, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,26 +89,20 @@ namespace Barotrauma
|
||||
return wasPut;
|
||||
}
|
||||
|
||||
protected override void CreateNetworkEvent()
|
||||
public override void CreateNetworkEvent()
|
||||
{
|
||||
int componentIndex = container.Item.components.IndexOf(container);
|
||||
int componentIndex = container.Item.GetComponentIndex(container);
|
||||
if (componentIndex == -1)
|
||||
{
|
||||
DebugConsole.Log("Creating a network event for the item \"" + container.Item + "\" failed, ItemContainer not found in components");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.Server != null)
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(Owner as IServerSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex });
|
||||
if (GameMain.NetworkMember.IsClient) { syncItemsDelay = 1.0f; }
|
||||
GameMain.NetworkMember.CreateEntityEvent(Owner as INetSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex });
|
||||
}
|
||||
#if CLIENT
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
syncItemsDelay = 1.0f;
|
||||
GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState, componentIndex });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void RemoveItem(Item item)
|
||||
|
||||
@@ -526,8 +526,8 @@ namespace Barotrauma
|
||||
|
||||
public PriceInfo GetPrice(Location location)
|
||||
{
|
||||
if (prices == null || !prices.ContainsKey(location.Type.Name.ToLowerInvariant())) return null;
|
||||
return prices[location.Type.Name.ToLowerInvariant()];
|
||||
if (prices == null || !prices.ContainsKey(location.Type.Identifier.ToLowerInvariant())) return null;
|
||||
return prices[location.Type.Identifier.ToLowerInvariant()];
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user