(c1468d39d) Overhauled docking interface (WIP)

This commit is contained in:
Joonas Rikkonen
2019-05-03 13:39:54 +03:00
parent 7eb0972d16
commit b551ae7999
101 changed files with 1249 additions and 2246 deletions
@@ -320,9 +320,8 @@ namespace Barotrauma
{
State = AIState.Idle;
}
else if (Character.Health < fleeHealthThreshold && SwarmBehavior == null)
else if (Character.Health < fleeHealthThreshold)
{
// Don't flee from damage if in a swarm.
State = AIState.Escape;
}
else if (targetingPriority != null)
@@ -331,6 +330,8 @@ namespace Barotrauma
}
}
LatchOntoAI?.Update(this, deltaTime);
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
{
State = AIState.Idle;
@@ -368,15 +369,8 @@ namespace Barotrauma
default:
throw new NotImplementedException();
}
LatchOntoAI?.Update(this, deltaTime);
IsSteeringThroughGap = false;
if (SwarmBehavior != null)
{
SwarmBehavior.IsActive = State == AIState.Idle && Character.CurrentHull == null;
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
SwarmBehavior?.Update(deltaTime);
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run));
}
@@ -499,7 +493,7 @@ namespace Barotrauma
}
else
{
if (!IsLatchedOnSub)
if (!IsProperlyLatchedOnSub)
{
UpdateWallTarget();
}
@@ -558,7 +552,7 @@ namespace Barotrauma
{
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
if (section?.gap != null && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
if (section?.gap != null && section.gap.IsRoomToRoom && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
{
return;
}
@@ -799,21 +793,9 @@ namespace Barotrauma
}
}
public bool IsSteeringThroughGap { get; private set; }
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
{
IsSteeringThroughGap = true;
SelectedAiTarget = wall.AiTarget;
wallTarget = null;
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
Hull targetHull = section.gap?.FlowTargetHull;
float distance = Vector2.Distance(Character.WorldPosition, targetWorldPos);
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
if (distance > maxDistance)
{
return false;
}
if (targetHull != null)
{
if (wall.IsHorizontal)
@@ -824,7 +806,16 @@ namespace Barotrauma
{
targetWorldPos.X = targetHull.WorldRect.Center.X;
}
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
if (steeringManager is IndoorsSteeringManager)
{
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
}
else
{
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos));
}
return true;
}
return false;
@@ -1056,43 +1047,19 @@ namespace Barotrauma
#region Targeting
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
private bool IsProperlyLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub && SelectedAiTarget?.Entity == wallTarget?.Structure;
//goes through all the AItargets, evaluates how preferable it is to attack the target,
//whether the Character can see/hear the target and chooses the most preferable target within
//sight/hearing range
public AITarget UpdateTargets(Character character, out TargetingPriority priority)
{
if ((SelectedAiTarget != null || wallTarget != null) && IsLatchedOnSub)
if (IsProperlyLatchedOnSub)
{
var wall = SelectedAiTarget.Entity as Structure;
if (wall == null)
{
wall = wallTarget?.Structure;
}
// The target is not a wall or it's not the same as we are attached to -> release
bool releaseTarget = wall == null || !wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB);
if (!releaseTarget)
{
for (int i = 0; i < wall.Sections.Length; i++)
{
if (CanPassThroughHole(wall, i))
{
releaseTarget = true;
}
}
}
if (releaseTarget)
{
SelectedAiTarget = null;
wallTarget = null;
LatchOntoAI.DeattachFromBody();
}
else if (SelectedAiTarget?.Entity == wallTarget?.Structure)
{
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
AITarget newTarget = null;
priority = null;
@@ -1175,7 +1142,7 @@ namespace Barotrauma
else if (target.Entity != null)
{
//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; }
if (character.CurrentHull != null && target.Entity is Hull) continue;
Door door = null;
if (target.Entity is Item item)
@@ -1204,37 +1171,23 @@ namespace Barotrauma
// Ignore structures that doesn't have a body (not walls)
continue;
}
if (s.IsPlatform)
{
continue;
}
if (character.CurrentHull != null)
{
// Ignore walls when inside.
continue;
}
valueModifier = 1;
float wallMaxHealth = 400; // Anything more than this is ignored -> 200 = 1
// Prefer weaker targets.
valueModifier *= MathHelper.Lerp(1.5f, 0.5f, MathUtils.InverseLerp(0, 1, s.Health / wallMaxHealth));
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 1 : 0;
if (aggressiveBoarding)
{
var hulls = s.Submarine.GetHulls(false);
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
if (section.gap != null)
if (CanPassThroughHole(s, i))
{
if (CanPassThroughHole(s, i))
{
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null && hulls.Any(h => h.Rect.Intersects(section.rect));
valueModifier *= leadsInside ? 5 : 0;
}
else
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
// Ignore walls that can be passed through
valueModifier = 0;
break;
}
else if (section.gap != null)
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
}
}
@@ -223,10 +223,8 @@ namespace Barotrauma
}
}
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
//only humanoids can climb ladders
if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
if (character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
{
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
@@ -236,7 +234,7 @@ namespace Barotrauma
}
var collider = character.AnimController.Collider;
if (character.IsClimbing && !isDiving)
if (character.IsClimbing)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
@@ -280,12 +278,6 @@ namespace Barotrauma
}
else if (character.AnimController.InWater)
{
// If the character is underwater, we don't need the ladders anymore
if (character.IsClimbing && isDiving)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
}
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
{
currentPath.SkipToNextNode();
@@ -54,7 +54,7 @@ namespace Barotrauma
get { return attachJoints.Count > 0; }
}
public bool IsAttachedToSub => IsAttached && (attachTargetBody?.UserData is Submarine || attachTargetBody?.UserData is Entity entity && entity.Submarine != null);
public bool IsAttachedToSub => IsAttached && attachTargetBody?.UserData is Entity entity && (entity is Submarine sub || entity?.Submarine != null);
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
{
@@ -190,7 +190,7 @@ namespace Barotrauma
case AIController.AIState.Attack:
if (enemyAI.AttackingLimb != null)
{
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
if (attachToSub && wallAttachPos != Vector2.Zero && attachTargetBody != null)
{
// is not attached or is attached to something else
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
@@ -14,16 +14,11 @@ namespace Barotrauma
private float maxDistFromCenter;
private float cohesion;
public List<AICharacter> Members { get; private set; } = new List<AICharacter>();
public HashSet<AICharacter> ActiveMembers { get; private set; } = new HashSet<AICharacter>();
private List<AICharacter> members = new List<AICharacter>();
private EnemyAIController ai;
private AIController ai;
public bool IsActive { get; set; }
public bool IsEnoughMembers => ActiveMembers.Count > 1;
public SwarmBehavior(XElement element, EnemyAIController ai)
public SwarmBehavior(XElement element, AIController ai)
{
this.ai = ai;
minDistFromClosest = ConvertUnits.ToSimUnits(element.GetAttributeFloat("mindistfromclosest", 10.0f));
@@ -37,36 +32,21 @@ namespace Barotrauma
{
if (character.AIController is EnemyAIController enemyAI && enemyAI.SwarmBehavior != null)
{
enemyAI.SwarmBehavior.Members = swarm.ToList();
enemyAI.SwarmBehavior.members = swarm.ToList();
}
}
}
public void Refresh()
public void Update(float deltaTime)
{
Members.RemoveAll(m => m.IsDead || m.Removed);
foreach (var member in Members)
{
if (!member.AIController.Enabled && member.IsRemotePlayer || Character.Controlled == member || !((EnemyAIController)member.AIController).SwarmBehavior.IsActive)
{
ActiveMembers.Remove(member);
}
else
{
ActiveMembers.Add(member);
}
}
}
members.RemoveAll(m => m.IsDead || m.Removed);
if (members.Count < 2) { return; }
public void UpdateSteering(float deltaTime)
{
if (!IsActive) { return; }
if (!IsEnoughMembers) { return; }
//calculate the "center of mass" of the swarm and the distance to the closest character in the swarm
float closestDistSqr = float.MaxValue;
Vector2 center = Vector2.Zero;
AICharacter closest = null;
foreach (AICharacter member in Members)
foreach (AICharacter member in members)
{
center += member.SimPosition;
if (member == ai.Character) { continue; }
@@ -77,7 +57,7 @@ namespace Barotrauma
closest = member;
}
}
center /= Members.Count;
center /= members.Count;
if (closest == null) { return; }
@@ -103,11 +83,11 @@ namespace Barotrauma
if (cohesion > 0.0f)
{
Vector2 avgVel = Vector2.Zero;
foreach (AICharacter member in Members)
foreach (AICharacter member in members)
{
avgVel += member.AnimController.TargetMovement;
}
avgVel /= Members.Count;
avgVel /= members.Count;
ai.SteeringManager.SteeringManual(deltaTime, avgVel * cohesion);
}
}
@@ -293,27 +293,6 @@ namespace Barotrauma
return;
}
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
//stop dragging if there's something between the pull limb and the target
Vector2 sourceSimPos = mouthLimb.SimPosition;
Vector2 targetSimPos = target.SimPosition;
if (character.Submarine != null && character.SelectedCharacter.Submarine == null)
{
targetSimPos -= character.Submarine.SimPosition;
}
else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
{
sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition;
}
var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
if (body != null)
{
character.DeselectCharacter();
return;
}
}
Character targetCharacter = target;
float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f;
eatTimer += deltaTime * eatSpeed;
@@ -751,6 +730,22 @@ namespace Barotrauma
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
{
//make sure the angle "has the same number of revolutions" as the reference limb
//(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
while (referenceLimb.Rotation - angle > MathHelper.TwoPi)
{
angle += MathHelper.TwoPi;
}
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
public override void Flip()
{
base.Flip();
@@ -560,8 +560,7 @@ namespace Barotrauma
//TODO: take into account that the feet aren't necessarily in CurrentHull
//full slowdown (1.5f) when water is up to the torso
surfaceY = ConvertUnits.ToSimUnits(currentHull.Surface);
float bottomPos = Math.Max(colliderPos.Y, currentHull.Rect.Y - currentHull.Rect.Height);
slowdownAmount = MathHelper.Clamp((surfaceY - bottomPos) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
slowdownAmount = MathHelper.Clamp((surfaceY - colliderPos.Y) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
}
float maxSpeed = Math.Max(TargetMovement.Length() - slowdownAmount, 1.0f);
@@ -426,7 +426,7 @@ namespace Barotrauma
}
}
public bool CanSpeak;
private bool canSpeak;
private bool speechImpedimentSet;
@@ -436,7 +436,7 @@ namespace Barotrauma
{
get
{
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
if (!canSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
return speechImpediment;
}
set
@@ -710,7 +710,7 @@ namespace Barotrauma
displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true);
IsHumanoid = doc.Root.GetAttributeBool("humanoid", false);
CanSpeak = doc.Root.GetAttributeBool("canspeak", false);
canSpeak = doc.Root.GetAttributeBool("canspeak", false);
needsAir = doc.Root.GetAttributeBool("needsair", false);
Noise = doc.Root.GetAttributeFloat("noise", 100f);
@@ -853,7 +853,7 @@ namespace Barotrauma
{
if (characterConfigFiles == null)
{
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character, searchAllContentPackages: true);
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character);
}
return characterConfigFiles;
}
@@ -2555,9 +2555,11 @@ namespace Barotrauma
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
#endif
CharacterList.Remove(this);
#if CLIENT
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
#endif
if (Controlled == this) { Controlled = null; }
CharacterList.Remove(this);
if (Inventory != null)
{
@@ -399,7 +399,7 @@ namespace Barotrauma
{
ID = idCounter;
idCounter++;
Name = element.GetAttributeString("name", "");
Name = element.GetAttributeString("name", "unnamed");
string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant();
File = element.GetAttributeString("file", "");
SourceElement = GetConfig(File).Root;
@@ -423,29 +423,6 @@ namespace Barotrauma
element.GetAttributeInt("beardindex", -1),
element.GetAttributeInt("moustacheindex", -1),
element.GetAttributeInt("faceattachmentindex", -1));
if (string.IsNullOrEmpty(Name))
{
if (SourceElement.Element("name") != null)
{
string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
Name = ToolBox.GetRandomLine(firstNamePath);
}
string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (Name != "") Name += " ";
Name += ToolBox.GetRandomLine(lastNamePath);
}
}
}
StartItemsGiven = element.GetAttributeBool("startitemsgiven", false);
string personalityName = element.GetAttributeString("personality", "");
ragdollFileName = element.GetAttributeString("ragdoll", string.Empty);
@@ -217,7 +217,6 @@ namespace Barotrauma
{
return corePackageRequiredFiles.All(fileType => Files.Any(file => file.Type == fileType));
}
public bool ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes)
{
missingContentTypes = new List<ContentType>();
@@ -231,25 +230,6 @@ namespace Barotrauma
return missingContentTypes.Count == 0;
}
/// <summary>
/// Make sure all the files defined in the content package are present
/// </summary>
/// <returns></returns>
public bool VerifyFiles(out List<string> errorMessages)
{
errorMessages = new List<string>();
foreach (ContentFile file in Files)
{
if (!File.Exists(file.Path))
{
errorMessages.Add("File \"" + file.Path + "\" not found.");
continue;
}
}
return errorMessages.Count == 0;
}
public static ContentPackage CreatePackage(string name, string path, bool corePackage)
{
ContentPackage newPackage = new ContentPackage()
@@ -418,13 +398,6 @@ namespace Barotrauma
return path == "Mods";
}
}
/// <summary>
/// Are mods allowed to install a file into the specified path. If a content package XML includes files
/// with a prohibited path, they are treated as references to external files. For example, a mod could include
/// some vanilla files in the XML, in which case the game will simply use the vanilla files present in the game folder.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool IsModFilePathAllowed(string path)
{
while (true)
@@ -449,6 +422,16 @@ namespace Barotrauma
return contentPackages.SelectMany(f => f.Files).Where(f => f.Type == type).Select(f => f.Path);
}
public IEnumerable<string> GetFilesOfType(ContentType type)
{
return Files.Where(f => f.Type == type).Select(f => f.Path);
}
public static IEnumerable<string> GetFilesOfType(IEnumerable<ContentPackage> contentPackages, ContentType type)
{
return contentPackages.SelectMany(f => f.Files).Where(f => f.Type == type).Select(f => f.Path);
}
public IEnumerable<string> GetFilesOfType(ContentType type)
{
return Files.Where(f => f.Type == type).Select(f => f.Path);
@@ -34,33 +34,14 @@ namespace Barotrauma
{
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
//bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
//for (int i = 0; i < monsterCount; i++)
//{
// monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false));
//}
//monsters.ForEach(m => m.Enabled = false);
//SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
//sonarPositions.Add(spawnPos);
float offsetAmount = 500;
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
for (int i = 0; i < monsterCount; i++)
{
CoroutineManager.InvokeAfter(() =>
{
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
var monster = Character.Create(monsterFile, spawnPos + Rand.Vector(offsetAmount, Rand.RandSync.Server), i.ToString(), null, isClient, true, true);
monster.Enabled = false;
monsters.Add(monster);
if (monsters.Count == monsterCount)
{
//this will do nothing if the monsters have no swarm behavior defined,
//otherwise it'll make the spawned characters act as a swarm
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
sonarPositions.Add(spawnPos);
}
}, Rand.Range(0f, monsterCount / 2, Rand.RandSync.Server));
monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), null, isClient, true, false));
}
monsters.ForEach(m => m.Enabled = false);
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
sonarPositions.Add(spawnPos);
}
public override void Update(float deltaTime)
@@ -604,14 +604,13 @@ namespace Barotrauma
}
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
bool packageOk = contentPackage.VerifyFiles(out List<string> errorMessages);
if (!packageOk)
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\":\n" + string.Join("\n", errorMessages));
continue;
}
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);
}
}
@@ -971,14 +970,13 @@ namespace Barotrauma
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
bool packageOk = contentPackage.VerifyFiles(out List<string> errorMessages);
if (!packageOk)
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\":\n" + string.Join("\n", errorMessages));
continue;
}
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);
}
}
@@ -293,35 +293,10 @@ namespace Barotrauma.Items.Components
//steer closer if almost in range
if (dist > Range)
{
Vector2 standPos = new Vector2(Math.Sign(-fromItemToLeak.X), Math.Sign(-fromItemToLeak.Y)) / 2;
if (!character.AnimController.InWater)
{
if (leak.IsHorizontal)
{
standPos.X *= 2;
standPos.Y = 0;
}
else
{
standPos.X = 0;
}
}
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
{
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
{
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
}
else
{
character.AIController.SteeringManager.SteeringSeek(standPos);
}
}
else
{
character.AIController.SteeringManager.SteeringSeek(standPos);
}
Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f);
standPos = leak.WorldPosition + standPos * Range;
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
}
else
{
@@ -330,29 +305,30 @@ namespace Barotrauma.Items.Components
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
}
else if (dist <= Range)
{
// In range
character.AIController.SteeringManager.Reset();
}
else
{
return false;
character.AIController.SteeringManager.Reset();
}
}
sinTime += deltaTime;
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
if (item.RequireAimToUse)
{
character.SetInput(InputType.Aim, false, true);
}
// Press the trigger only when the tool is approximately facing the target.
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
if (angle < MathHelper.PiOver4)
// If the character is climbing, ignore the check, because we cannot aim while climbing.
if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
{
character.SetInput(InputType.Shoot, false, true);
Use(deltaTime, character);
}
else
{
sinTime -= deltaTime * 2;
}
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
@@ -801,7 +801,10 @@ namespace Barotrauma.Items.Components
string msg = TextManager.Get(Msg, true);
if (msg != null)
{
msg = TextManager.ParseInputTypes(msg);
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
}
DisplayMsg = msg;
}
else
@@ -13,11 +13,6 @@ namespace Barotrauma.Items.Components
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
{
get { return inputContainer; }
}
public ItemContainer OutputContainer
{
get { return outputContainer; }
@@ -23,16 +23,6 @@ namespace Barotrauma.Items.Components
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
{
get { return inputContainer; }
}
public ItemContainer OutputContainer
{
get { return outputContainer; }
}
private float progressState;
public Fabricator(Item item, XElement element)
@@ -108,23 +98,7 @@ namespace Barotrauma.Items.Components
{
return (picker != null);
}
public void RemoveFabricationRecipes(List<string> allowedIdentifiers)
{
for (int i = 0; i < fabricationRecipes.Count; i++)
{
if (!allowedIdentifiers.Contains(fabricationRecipes[i].TargetItem.Identifier))
{
fabricationRecipes.RemoveAt(i);
i--;
}
}
CreateRecipes();
}
partial void CreateRecipes();
private void StartFabricating(FabricationRecipe selectedItem, Character user)
{
if (selectedItem == null) return;
@@ -259,7 +259,7 @@ namespace Barotrauma.Items.Components
int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12);
if (clockDir == 0) clockDir = 12;
return TextManager.Get("roomname.subdiroclock").Replace("[dir]", clockDir.ToString());
return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString());
}
private Vector2 GetTransducerCenter()
@@ -206,16 +206,6 @@ namespace Barotrauma
return true;
}
public bool IsFull()
{
for (int i = 0; i < capacity; i++)
{
if (Items[i] == null) return false;
}
return true;
}
protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent)
{
if (item?.ParentInventory == null || Items[index] == null) return false;
@@ -1149,10 +1149,6 @@ namespace Barotrauma
{
body.SetTransform(body.SimPosition - Submarine.SimPosition, body.Rotation);
}
else if (Submarine != null && prevSub != null && Submarine != prevSub)
{
body.SetTransform(body.SimPosition + prevSub.SimPosition - Submarine.SimPosition, body.Rotation);
}
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
@@ -40,7 +40,7 @@ namespace Barotrauma
flames = true;
underwaterBubble = true;
}
public Explosion(XElement element, string parentDebugName)
{
attack = new Attack(element, parentDebugName + ", Explosion");
@@ -62,16 +62,6 @@ namespace Barotrauma
CameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f);
}
public void DisableParticles()
{
sparks = false;
shockwave = false;
smoke = false;
flash = false;
flames = false;
underwaterBubble = false;
}
public List<Triplet<Explosion, Vector2, float>> GetRecentExplosions(float maxSecondsAgo)
{
return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo);
@@ -14,15 +14,15 @@ namespace Barotrauma
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 5.0f;
private Hull hull;
protected Hull hull;
protected Vector2 position;
protected Vector2 size;
private Vector2 position;
private Vector2 size;
private Entity Submarine;
protected bool removed;
private bool removed;
#if CLIENT
private List<Decal> burnDecals = new List<Decal>();
@@ -182,16 +182,6 @@ namespace Barotrauma
}
}
protected virtual void ReduceOxygen(float deltaTime)
{
hull.Oxygen -= size.X * deltaTime * OxygenConsumption;
}
protected virtual void AdjustXPos(float growModifier, float deltaTime)
{
position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
}
partial void UpdateProjSpecific(float growModifier);
private void OnChangeHull(Vector2 pos, Hull particleHull)
@@ -138,9 +138,6 @@ namespace Barotrauma
public Submarine StartOutpost { get; private set; }
public Submarine EndOutpost { get; private set; }
private Submarine preSelectedStartOutpost;
private Submarine preSelectedEndOutpost;
public string Seed
{
get { return seed; }
@@ -212,7 +209,7 @@ namespace Barotrauma
/// </summary>
/// <param name="difficulty">A scalar between 0-100</param>
/// <param name="sizeFactor">A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used)</param>
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, Submarine startOutpost = null, Submarine endOutPost = null)
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
: base(null)
{
@@ -228,9 +225,6 @@ namespace Barotrauma
(width / GridCellSize) * GridCellSize,
(generationParams.Height / GridCellSize) * GridCellSize);
preSelectedStartOutpost = startOutpost;
preSelectedEndOutpost = endOutPost;
//remove from entity dictionary
base.Remove();
}
@@ -1516,24 +1510,14 @@ namespace Barotrauma
continue;
}
//only create a starting outpost in campaign and tutorial modes
if (!IsModeStartOutpostCompatible() && ((i == 0) == !Mirrored))
//only create a starting outpost in campaign mode
if (GameMain.GameSession?.GameMode as CampaignMode == null && ((i == 0) == !Mirrored))
{
continue;
}
Submarine outpost = null;
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
{
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server);
outpost = new Submarine(outpostFile, tryLoad: false);
}
else
{
outpost = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
}
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server);
var outpost = new Submarine(outpostFile, tryLoad: false);
outpost.Load(unloadPrevious: false);
outpost.MakeOutpost();
@@ -1585,15 +1569,6 @@ namespace Barotrauma
}
}
private bool IsModeStartOutpostCompatible()
{
#if CLIENT
return GameMain.GameSession?.GameMode as CampaignMode != null || GameMain.GameSession?.GameMode as TutorialMode != null;
#else
return GameMain.GameSession?.GameMode as CampaignMode != null;
#endif
}
public override void Remove()
{
base.Remove();
@@ -635,6 +635,24 @@ namespace Barotrauma
var character = ((Limb)f2.Body.UserData).character;
if (character.DisableImpactDamageTimer > 0.0f || ((Limb)f2.Body.UserData).Mass < 100.0f) return true;
}
if (!Prefab.Platform && Prefab.StairDirection == Direction.None)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
int section = FindSectionIndex(pos);
if (section > -1)
{
Vector2 normal = contact.Manifold.LocalNormal;
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal) * f2.Body.Mass * 0.1f;
if (impact < 10.0f) return true;
#if CLIENT
SoundPlayer.PlayDamageSound("StructureBlunt", impact, SectionPosition(section, true), tags: Tags);
#endif
AddDamage(section, impact);
}
}
OnImpactProjSpecific(f1, f2, contact);
@@ -431,12 +431,6 @@ namespace Barotrauma
else if (ic is Pickable pickable)
{
//prevent picking up (or deattaching) items
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode)
{
continue;
}
#endif
pickable.CanBePicked = false;
pickable.CanBeSelected = false;
}
@@ -542,6 +536,20 @@ namespace Barotrauma
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
else
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
{
//no walls found at either side, just use the initial spawnpos and hope for the best
}
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f + subDockingPortOffset;
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
@@ -468,7 +468,7 @@ namespace Barotrauma
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
targetPos = character.WorldPosition;
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 500.0f);
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 200.0f);
if (adjacentGap == null) return true;
if (newHull != null)
@@ -102,7 +102,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, condition));
@@ -115,7 +115,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, condition));
@@ -401,7 +401,7 @@ namespace Barotrauma.Networking
public bool AllowRagdollButton
{
get;
set;
private set;
}
[Serialize(true, true)]
@@ -46,15 +46,7 @@ namespace Barotrauma
if (File.Exists(filePath))
{
try
{
doc = XDocument.Load(filePath, LoadOptions.SetBaseUri);
}
catch
{
return null;
}
doc = XDocument.Load(filePath, LoadOptions.SetBaseUri);
if (doc.Root == null) return null;
}
@@ -118,14 +118,160 @@ namespace Barotrauma
}
}
public static string ParseInputTypes(string text)
public static string GetFormatted(string textTag, bool returnNull = false, params object[] args)
{
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
string text = Get(textTag, returnNull);
if (text == null || text.Length == 0)
{
text = text.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
text = text.Replace("[InputType." + inputType.ToString() + "]", GameMain.Config.KeyBind(inputType).ToString());
if (returnNull)
{
return null;
}
else
{
DebugConsole.ThrowError("Text \"" + textTag + "\" not found.");
return textTag;
}
}
return text;
return string.Format(text, args);
}
// Format: ServerMessage.Identifier1/ServerMessage.Indentifier2~[variable1]=value~[variable2]=value
public static string GetServerMessage(string serverMessage)
{
if (!textPacks.ContainsKey(Language))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
string[] messages = serverMessage.Split('/');
try
{
for (int i = 0; i < messages.Length; i++)
{
if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate
{
if (messages[i].Contains(" ")) continue; // Spaces found, do not translate
string msg = Get(messages[i], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
}
}
else
{
string[] messageWithVariables = messages[i].Split('~');
string msg = Get(messageWithVariables[0], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
}
else
{
continue; // No translation found, probably caused by player input -> skip variable handling
}
// First index is always the message identifier -> start at 1
for (int j = 1; j < messageWithVariables.Length; j++)
{
string[] variableAndValue = messageWithVariables[j].Split('=');
messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]);
}
}
}
string translatedServerMessage = string.Empty;
for (int i = 0; i < messages.Length; i++)
{
translatedServerMessage += messages[i];
}
return translatedServerMessage;
}
catch (IndexOutOfRangeException exception)
{
string errorMsg = "Failed to translate server message \"" + serverMessage + "\".";
#if DEBUG
DebugConsole.ThrowError(errorMsg, exception);
#endif
GameAnalyticsManager.AddErrorEventOnce("TextManager.GetServerMessage:" + serverMessage, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return errorMsg;
}
}
public static bool IsServerMessageWithVariables(string message)
{
for (int i = 0; i < serverMessageCharacters.Length; i++)
{
if (!message.Contains(serverMessageCharacters[i])) return false;
}
return true;
}
public static List<string> GetAll(string textTag)
{
if (!textPacks.ContainsKey(Language))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
List<string> allText;
foreach (TextPack textPack in textPacks[Language])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
}
return null;
}
public static List<KeyValuePair<string, string>> GetAllTagTextPairs()
{
if (!textPacks.ContainsKey(Language))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
List<KeyValuePair<string, string>> allText = new List<KeyValuePair<string, string>>();
foreach (TextPack textPack in textPacks[Language])
{
allText.AddRange(textPack.GetAllTagTextPairs());
}
return allText;
}
public static string GetFormatted(string textTag, bool returnNull = false, params object[] args)
@@ -795,6 +795,18 @@ namespace Barotrauma
return new Vector2((float)x, (float)y);
}
/// <summary>
/// Rotates a point in 2d space around the origin
/// </summary>
public static Vector2 RotatePoint(Vector2 point, float radians)
{
var sin = Math.Sin(radians);
var cos = Math.Cos(radians);
var x = (cos * point.X) - (sin * point.Y);
var y = (sin * point.X) + (cos * point.Y);
return new Vector2((float)x, (float)y);
}
/// <summary>
/// Returns the corners of an imaginary rectangle.
/// Unlike the XNA rectangle, this can be rotated with the up parameter.