2f107db...5202af9

This commit is contained in:
Joonas Rikkonen
2019-03-18 21:42:26 +02:00
parent 58c92888b7
commit 044fd3344b
395 changed files with 27417 additions and 19754 deletions
@@ -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,7 +1,6 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
@@ -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":
@@ -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) +
@@ -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;
}
}
}
@@ -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)
{
@@ -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);
}
}
}
}
}
@@ -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;
}
}
}
@@ -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()];
}
}
}
@@ -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