Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -9,7 +9,8 @@ namespace Barotrauma
|
||||
|
||||
public bool CausedByPsychosis;
|
||||
|
||||
public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) : base(worldPosition, spawningHull, isNetworkMessage)
|
||||
public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) :
|
||||
base(worldPosition, spawningHull, sourceCharacter: null, isNetworkMessage: isNetworkMessage)
|
||||
{
|
||||
this.maxSize = maxSize;
|
||||
DamagesItems = false;
|
||||
|
||||
@@ -91,7 +91,12 @@ namespace Barotrauma
|
||||
get { return hull; }
|
||||
}
|
||||
|
||||
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
|
||||
/// <summary>
|
||||
/// Which character caused this fire (if any)?
|
||||
/// </summary>
|
||||
public readonly Character SourceCharacter;
|
||||
|
||||
public FireSource(Vector2 worldPosition, Hull spawningHull = null, Character sourceCharacter = null, bool isNetworkMessage = false)
|
||||
{
|
||||
hull = Hull.FindHull(worldPosition, spawningHull);
|
||||
if (hull == null || worldPosition.Y < hull.WorldSurface) { return; }
|
||||
@@ -109,6 +114,8 @@ namespace Barotrauma
|
||||
position -= Submarine.Position;
|
||||
}
|
||||
|
||||
SourceCharacter = sourceCharacter;
|
||||
|
||||
#if CLIENT
|
||||
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull?.Submarine);
|
||||
#endif
|
||||
@@ -306,8 +313,12 @@ namespace Barotrauma
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
c.LastDamageSource = null;
|
||||
c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(), 0.0f, false, 0.0f);
|
||||
c.LastDamageSource = SourceCharacter;
|
||||
c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(),
|
||||
stun: 0.0f,
|
||||
playSound: false,
|
||||
attackImpulse: Vector2.Zero,
|
||||
attacker: SourceCharacter);
|
||||
}
|
||||
#if CLIENT
|
||||
//let clients display the client-side damage immediately, otherwise they may not be able to react to the damage fast enough
|
||||
|
||||
@@ -442,6 +442,11 @@ namespace Barotrauma
|
||||
|
||||
public List<DummyFireSource> FakeFireSources { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by conditionals
|
||||
/// </summary>
|
||||
public int FireCount => FireSources?.Count ?? 0;
|
||||
|
||||
public BallastFloraBehavior BallastFlora { get; set; }
|
||||
|
||||
public Hull(Rectangle rectangle)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
Vector2 WorldPosition { get; }
|
||||
float Health { get; }
|
||||
|
||||
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true);
|
||||
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true);
|
||||
|
||||
|
||||
public readonly struct AttackEventData
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Barotrauma
|
||||
partial void AddDamageProjSpecific(float damage, Vector2 worldPosition);
|
||||
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true)
|
||||
{
|
||||
AddDamage(attack.StructureDamage, worldPosition);
|
||||
return new AttackResult(attack.StructureDamage);
|
||||
|
||||
@@ -115,6 +115,20 @@ namespace Barotrauma
|
||||
Ruin = null;
|
||||
Cave = cave;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Caves, ruins, outposts and similar enclosed areas
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsEnclosedArea()
|
||||
{
|
||||
return
|
||||
PositionType == PositionType.Cave ||
|
||||
PositionType == PositionType.Ruin ||
|
||||
PositionType == PositionType.Outpost ||
|
||||
PositionType == PositionType.BeaconStation ||
|
||||
PositionType == PositionType.AbyssCave;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TunnelType
|
||||
@@ -619,14 +633,14 @@ namespace Barotrauma
|
||||
{
|
||||
endHole = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { startPosition, startExitPosition, new Point(0, Size.Y) },
|
||||
new List<Point>() { startPosition, new Point(0, startPosition.Y) },
|
||||
minWidth, parentTunnel: mainPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
endHole = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { endPosition, endExitPosition, Size },
|
||||
new List<Point>() { endPosition, new Point(Size.X, endPosition.Y) },
|
||||
minWidth, parentTunnel: mainPath);
|
||||
}
|
||||
Tunnels.Add(endHole);
|
||||
@@ -930,6 +944,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (AbyssIsland abyssIsland in AbyssIslands)
|
||||
{
|
||||
abyssIsland.Cells.RemoveAll(c => c.CellType == CellType.Path);
|
||||
cells.AddRange(abyssIsland.Cells);
|
||||
}
|
||||
|
||||
@@ -1726,7 +1741,9 @@ namespace Barotrauma
|
||||
{
|
||||
bool tooClose = false;
|
||||
|
||||
if (cell.IsPointInsideAABB(position, margin: minDistance))
|
||||
//if the cell is very large, the position can be far away from the edges while being inside the cell
|
||||
//so we need to check that here too
|
||||
if (cell.IsPointInside(position))
|
||||
{
|
||||
tooClose = true;
|
||||
}
|
||||
@@ -3257,13 +3274,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
|
||||
int tries = 0;
|
||||
do
|
||||
{
|
||||
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos, filter);
|
||||
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out InterestingPosition potentialPos, filter);
|
||||
|
||||
Vector2 offset = Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.ServerAndClient), Rand.RandSync.ServerAndClient);
|
||||
Vector2 startPos = potentialPos.Position.ToVector2();
|
||||
if (!IsPositionInsideWall(startPos + offset))
|
||||
{
|
||||
startPos += offset;
|
||||
@@ -3271,14 +3288,18 @@ namespace Barotrauma
|
||||
|
||||
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
|
||||
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(startPos),
|
||||
ConvertUnits.ToSimUnits(endPos),
|
||||
ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)),
|
||||
Physics.CollisionLevel | Physics.CollisionWall) != null)
|
||||
//try to find a level wall below the position unless the position is indoors
|
||||
if (!potentialPos.IsEnclosedArea())
|
||||
{
|
||||
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall;
|
||||
break;
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(startPos),
|
||||
ConvertUnits.ToSimUnits(endPos),
|
||||
ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)),
|
||||
Physics.CollisionLevel | Physics.CollisionWall)?.UserData is VoronoiCell)
|
||||
{
|
||||
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tries++;
|
||||
@@ -3293,25 +3314,25 @@ namespace Barotrauma
|
||||
return position;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Vector2 awayPoint, float minDistFromPoint, Func<InterestingPosition, bool> filter = null)
|
||||
public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Vector2 awayPoint, float minDistFromPoint, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, awayPoint, minDistFromPoint, filter);
|
||||
position = pos.ToVector2();
|
||||
position = default;
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out position, awayPoint, minDistFromPoint, filter);
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
|
||||
{
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning);
|
||||
position = pos.ToVector2();
|
||||
position = default;
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out position, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning);
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
|
||||
{
|
||||
if (!PositionsOfInterest.Any())
|
||||
{
|
||||
position = new Point(Size.X / 2, Size.Y / 2);
|
||||
position = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3323,7 +3344,20 @@ namespace Barotrauma
|
||||
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath) || positionType.HasFlag(PositionType.Abyss) ||
|
||||
positionType.HasFlag(PositionType.Cave) || positionType.HasFlag(PositionType.AbyssCave))
|
||||
{
|
||||
suitablePositions.RemoveAll(p => IsPositionInsideWall(p.Position.ToVector2()));
|
||||
#if DEBUG
|
||||
for (int i = 0; i < PositionsOfInterest.Count; i++)
|
||||
{
|
||||
var pos = PositionsOfInterest[i];
|
||||
if (!suitablePositions.Contains(pos)) { continue; }
|
||||
if (IsInvalid(pos))
|
||||
{
|
||||
pos.IsValid = false;
|
||||
PositionsOfInterest[i] = pos;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
suitablePositions.RemoveAll(p => IsInvalid(p));
|
||||
bool IsInvalid(InterestingPosition p) => IsPositionInsideWall(p.Position.ToVector2());
|
||||
}
|
||||
if (!suitablePositions.Any())
|
||||
{
|
||||
@@ -3335,7 +3369,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#endif
|
||||
}
|
||||
position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position;
|
||||
position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))];
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3361,14 +3395,14 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#endif
|
||||
float maxDist = 0.0f;
|
||||
position = suitablePositions.First().Position;
|
||||
position = suitablePositions.First();
|
||||
foreach (InterestingPosition pos in suitablePositions)
|
||||
{
|
||||
float dist = Submarine.Loaded.Sum(s =>
|
||||
Submarine.MainSubs.Contains(s) ? Vector2.DistanceSquared(s.WorldPosition, pos.Position.ToVector2()) : 0.0f);
|
||||
if (dist > maxDist)
|
||||
{
|
||||
position = pos.Position;
|
||||
position = pos;
|
||||
maxDist = dist;
|
||||
}
|
||||
}
|
||||
@@ -3376,7 +3410,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)].Position;
|
||||
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3933,12 +3967,28 @@ namespace Barotrauma
|
||||
{
|
||||
var totalSW = new Stopwatch();
|
||||
totalSW.Start();
|
||||
|
||||
var wreckFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<WreckFile>())
|
||||
.OrderBy(f => f.UintIdentifier).ToList();
|
||||
|
||||
for (int i = wreckFiles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var wreckFile = wreckFiles[i];
|
||||
var wreckInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsWreck);
|
||||
var matchingInfo = wreckInfos.SingleOrDefault(info => info.FilePath == wreckFile.Path.Value);
|
||||
Debug.Assert(matchingInfo != null);
|
||||
if (matchingInfo?.WreckInfo is WreckInfo wreckInfo)
|
||||
{
|
||||
if (Difficulty < wreckInfo.MinLevelDifficulty || Difficulty > wreckInfo.MaxLevelDifficulty)
|
||||
{
|
||||
wreckFiles.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wreckFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError("No wreck files found in the selected content packages!");
|
||||
DebugConsole.ThrowError($"No wreck files found for the level difficulty {LevelData.Difficulty}!");
|
||||
Wrecks = new List<Submarine>();
|
||||
return;
|
||||
}
|
||||
@@ -4072,7 +4122,7 @@ namespace Barotrauma
|
||||
|
||||
if (location != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Generating an outpost for the {(isStart ? "start" : "end")} of the level... (Location: {location.Name}, level type: {LevelData.Type})");
|
||||
DebugConsole.NewMessage($"Generating an outpost for the {(isStart ? "start" : "end")} of the level... (Location: {location.DisplayName}, level type: {LevelData.Type})");
|
||||
outpost = OutpostGenerator.Generate(outpostGenerationParams, location, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost, LevelData.AllowInvalidOutpost);
|
||||
}
|
||||
else
|
||||
@@ -4180,7 +4230,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1);
|
||||
Vector2 preferredSpawnPos = i == 0 ? StartPosition : EndPosition;
|
||||
//if we're placing the outpost at the end of the level, close to the bottom-right,
|
||||
//and there's a hole leading out the right side of the level, move the spawn position towards that hole.
|
||||
//Makes outpost placement a little nicer in levels with lots of verticality: if there's a tall vertical
|
||||
//shaft leading down to the end position, we don't want the outpost to be placed all the way up to wherever the
|
||||
//ceiling is at the top of that shaft.
|
||||
if (i == 1 && GenerationParams.CreateHoleNextToEnd &&
|
||||
preferredSpawnPos.X > Size.X * 0.75f &&
|
||||
preferredSpawnPos.Y < Size.Y * 0.25f)
|
||||
{
|
||||
preferredSpawnPos.X = (preferredSpawnPos.X + Size.X) / 2;
|
||||
}
|
||||
|
||||
spawnPos = outpost.FindSpawnPos(preferredSpawnPos, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1);
|
||||
if (Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
|
||||
@@ -4204,7 +4267,7 @@ namespace Barotrauma
|
||||
if (StartLocation != null)
|
||||
{
|
||||
outpost.TeamID = StartLocation.Type.OutpostTeam;
|
||||
outpost.Info.Name = StartLocation.Name;
|
||||
outpost.Info.Name = StartLocation.DisplayName.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4213,7 +4276,7 @@ namespace Barotrauma
|
||||
if (EndLocation != null)
|
||||
{
|
||||
outpost.TeamID = EndLocation.Type.OutpostTeam;
|
||||
outpost.Info.Name = EndLocation.Name;
|
||||
outpost.Info.Name = EndLocation.DisplayName.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4235,7 +4298,8 @@ namespace Barotrauma
|
||||
ContentFile contentFile = null;
|
||||
if (!string.IsNullOrEmpty(GenerationParams.ForceBeaconStation))
|
||||
{
|
||||
contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == GenerationParams.ForceBeaconStation);
|
||||
var contentPath = ContentPath.FromRaw(GenerationParams.ContentPackage, GenerationParams.ForceBeaconStation);
|
||||
contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == contentPath);
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find the beacon station \"{GenerationParams.ForceBeaconStation}\". Using a random one instead...");
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public LevelData(Location location, Map map, float difficulty)
|
||||
{
|
||||
Seed = location.BaseName + map.Locations.IndexOf(location);
|
||||
Seed = location.NameIdentifier.Value + map.Locations.IndexOf(location);
|
||||
Biome = location.Biome;
|
||||
Type = LevelType.Outpost;
|
||||
Difficulty = difficulty;
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true)
|
||||
{
|
||||
if (Health <= 0.0f) { return new AttackResult(0.0f); }
|
||||
|
||||
|
||||
+3
-1
@@ -526,12 +526,13 @@ namespace Barotrauma
|
||||
{
|
||||
List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
bool requireCaveSpawnPos = spawnPosType == LevelObjectPrefab.SpawnPosType.CaveWall;
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid || edge.OutsideLevel) { continue; }
|
||||
if (spawnPosType != LevelObjectPrefab.SpawnPosType.CaveWall && edge.NextToCave) { continue; }
|
||||
if (requireCaveSpawnPos != edge.NextToCave) { continue; }
|
||||
Vector2 normal = edge.GetNormal(cell);
|
||||
|
||||
Alignment edgeAlignment = 0;
|
||||
@@ -638,6 +639,7 @@ namespace Barotrauma
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
objectsInRange.Clear();
|
||||
if (objects != null)
|
||||
{
|
||||
foreach (LevelObject obj in objects)
|
||||
|
||||
@@ -55,8 +55,17 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<LocationConnection> Connections = new List<LocationConnection>();
|
||||
|
||||
private string baseName;
|
||||
public LocalizedString DisplayName { get; private set; }
|
||||
|
||||
public Identifier NameIdentifier => nameIdentifier;
|
||||
|
||||
private int nameFormatIndex;
|
||||
private Identifier nameIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// For backwards compatibility: a non-localizable name from the old text files.
|
||||
/// </summary>
|
||||
private string rawName;
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
@@ -75,10 +84,6 @@ namespace Barotrauma
|
||||
|
||||
public bool DisallowLocationTypeChanges;
|
||||
|
||||
public string BaseName { get => baseName; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Biome Biome { get; set; }
|
||||
|
||||
public Vector2 MapPosition { get; private set; }
|
||||
@@ -309,7 +314,7 @@ namespace Barotrauma
|
||||
if (!faction.IsEmpty && GameMain.GameSession.Campaign.GetFactionAffiliation(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
|
||||
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, new Identifier("all")));
|
||||
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, Tags.StatIdentifierTargetAll));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
|
||||
}
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||
@@ -484,7 +489,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (missionIndex < 0 || missionIndex >= availableMissions.Count)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to select a mission in location \"{Name}\". Mission index out of bounds ({missionIndex}, available missions: {availableMissions.Count})");
|
||||
DebugConsole.ThrowError($"Failed to select a mission in location \"{DisplayName}\". Mission index out of bounds ({missionIndex}, available missions: {availableMissions.Count})");
|
||||
break;
|
||||
}
|
||||
selectedMissions.Add(availableMissions[missionIndex]);
|
||||
@@ -536,15 +541,15 @@ namespace Barotrauma
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Location ({Name ?? "null"})";
|
||||
return $"Location ({DisplayName ?? "null"})";
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, LocationType forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
Type = OriginalType = forceLocationType ?? LocationType.Random(rand, zone, requireOutpost);
|
||||
Name = RandomName(Type, rand, existingLocations);
|
||||
CreateRandomName(Type, rand, existingLocations);
|
||||
MapPosition = mapPosition;
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
PortraitId = ToolBox.StringToInt(nameIdentifier.Value);
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
@@ -561,9 +566,21 @@ namespace Barotrauma
|
||||
GetTypeOrFallback(originalLocationTypeId, out LocationType originalType);
|
||||
OriginalType = originalType;
|
||||
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
nameIdentifier = element.GetAttributeIdentifier(nameof(nameIdentifier), "");
|
||||
if (nameIdentifier.IsEmpty)
|
||||
{
|
||||
//backwards compatibility
|
||||
rawName = element.GetAttributeString("basename", "");
|
||||
nameIdentifier = rawName.ToIdentifier();
|
||||
DisplayName = element.GetAttributeString("name", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
nameFormatIndex = element.GetAttributeInt(nameof(nameFormatIndex), 0);
|
||||
DisplayName = GetName(Type, nameFormatIndex, nameIdentifier);
|
||||
}
|
||||
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
@@ -639,9 +656,9 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationTypeId}\"!");
|
||||
Type ??= LocationType.Prefabs.First();
|
||||
|
||||
LevelData = new LevelData(element.Element("Level"), clampDifficultyToBiome: true);
|
||||
LevelData = new LevelData(element.GetChildElement("Level"), clampDifficultyToBiome: true);
|
||||
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
PortraitId = ToolBox.StringToInt(!rawName.IsNullOrEmpty() ? rawName : nameIdentifier.Value);
|
||||
|
||||
LoadStores(element);
|
||||
LoadMissions(element);
|
||||
@@ -659,15 +676,12 @@ namespace Barotrauma
|
||||
if (type == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find location type \"{identifier}\". Using location type \"None\" instead.");
|
||||
LocationType.Prefabs.TryGet("None".ToIdentifier(), out type);
|
||||
if (type == null)
|
||||
{
|
||||
type = LocationType.Prefabs.First();
|
||||
}
|
||||
LocationType.Prefabs.TryGet("None".ToIdentifier(), out type);
|
||||
type ??= LocationType.Prefabs.First();
|
||||
}
|
||||
if (type != null)
|
||||
{
|
||||
element.SetAttributeValue("type", type.Identifier);
|
||||
element.SetAttributeValue("type", type.Identifier.ToString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -690,7 +704,7 @@ namespace Barotrauma
|
||||
int locationTypeChangeIndex = subElement.GetAttributeInt("index", 0);
|
||||
if (locationTypeChangeIndex < 0 || locationTypeChangeIndex >= Type.CanChangeTo.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change in the location \"{Name}\". Location index out of bounds ({locationTypeChangeIndex}).");
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change in the location \"{DisplayName}\". Location index out of bounds ({locationTypeChangeIndex}).");
|
||||
continue;
|
||||
}
|
||||
PendingLocationTypeChange = (Type.CanChangeTo[locationTypeChangeIndex], timer, null);
|
||||
@@ -701,7 +715,7 @@ namespace Barotrauma
|
||||
var mission = MissionPrefab.Prefabs[missionIdentifier];
|
||||
if (mission == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change from the mission \"{missionIdentifier}\" in location \"{Name}\". Matching mission not found.");
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change from the mission \"{missionIdentifier}\" in location \"{DisplayName}\". Matching mission not found.");
|
||||
continue;
|
||||
}
|
||||
PendingLocationTypeChange = (mission.LocationTypeChangeOnCompleted, timer, mission);
|
||||
@@ -738,14 +752,27 @@ namespace Barotrauma
|
||||
|
||||
if (newType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{Name}\" to null.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{DisplayName}\" to null.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
Name = Type.NameFormats == null || !Type.NameFormats.Any() ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
if (rawName != null)
|
||||
{
|
||||
DebugConsole.Log($"Location {rawName} changed it's type from {Type} to {newType}");
|
||||
DisplayName =
|
||||
Type.NameFormats == null || !Type.NameFormats.Any() ?
|
||||
rawName :
|
||||
Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", rawName);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log($"Location {DisplayName.Value} changed it's type from {Type} to {newType}");
|
||||
DisplayName =
|
||||
Type.NameFormats == null || !Type.NameFormats.Any() ?
|
||||
TextManager.Get(nameIdentifier) :
|
||||
Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", TextManager.Get(nameIdentifier).Value);
|
||||
}
|
||||
|
||||
if (Type.HasOutpost && Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
@@ -776,11 +803,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync));
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync), invokingContentPackage: Type.ContentPackage);
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom(randSync));
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom(randSync), invokingContentPackage: Type.ContentPackage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,7 +825,7 @@ namespace Barotrauma
|
||||
AddMission(InstantiateMission(missionPrefab));
|
||||
}
|
||||
|
||||
public Mission UnlockMissionByIdentifier(Identifier identifier)
|
||||
public Mission UnlockMissionByIdentifier(Identifier identifier, ContentPackage invokingContentPackage = null)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; }
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
@@ -806,7 +833,8 @@ namespace Barotrauma
|
||||
var missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == identifier);
|
||||
if (missionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found.");
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found.",
|
||||
contentPackage: invokingContentPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -823,13 +851,13 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mission UnlockMissionByTag(Identifier tag, Random random = null)
|
||||
public Mission UnlockMissionByTag(Identifier tag, Random random = null, ContentPackage invokingContentPackage = null)
|
||||
{
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Contains(tag));
|
||||
if (matchingMissions.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.");
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.", contentPackage: invokingContentPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -841,7 +869,16 @@ namespace Barotrauma
|
||||
{
|
||||
suitableMissions = unusedMissions;
|
||||
}
|
||||
|
||||
var filteredMissions = suitableMissions.Where(m => LevelData.Difficulty >= m.MinLevelDifficulty && LevelData.Difficulty <= m.MaxLevelDifficulty);
|
||||
if (filteredMissions.None())
|
||||
{
|
||||
DebugConsole.AddWarning($"No suitable mission matching the level difficulty {LevelData.Difficulty} found with the tag \"{tag}\". Ignoring the restriction.",
|
||||
contentPackage: invokingContentPackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
suitableMissions = filteredMissions;
|
||||
}
|
||||
MissionPrefab missionPrefab =
|
||||
random != null ?
|
||||
ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, random) :
|
||||
@@ -854,12 +891,13 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
AddMission(mission);
|
||||
DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\".", debugOnly: true);
|
||||
DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\": {mission.Prefab.Identifier} (difficulty level: {LevelData.Difficulty})", debugOnly: true);
|
||||
return mission;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked.");
|
||||
DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked.",
|
||||
contentPackage: invokingContentPackage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,11 +1026,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (addInitialMissionsForType.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandomUnsynced());
|
||||
UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandomUnsynced(), invokingContentPackage: Type.ContentPackage);
|
||||
}
|
||||
if (addInitialMissionsForType.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandomUnsynced());
|
||||
UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandomUnsynced(), invokingContentPackage: Type.ContentPackage);
|
||||
}
|
||||
addInitialMissionsForType = null;
|
||||
}
|
||||
@@ -1050,12 +1088,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (!Type.HasHireableCharacters)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - the location has no hireable characters.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
DebugConsole.ThrowError("Cannot hire a character from location \"" + DisplayName + "\" - the location has no hireable characters.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
if (HireManager == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
DebugConsole.ThrowError("Cannot hire a character from location \"" + DisplayName + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1078,22 +1116,52 @@ namespace Barotrauma
|
||||
return HireManager.AvailableCharacters;
|
||||
}
|
||||
|
||||
private string RandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
private void CreateRandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (!type.ForceLocationName.IsNullOrEmpty())
|
||||
if (!type.ForceLocationName.IsEmpty)
|
||||
{
|
||||
baseName = type.ForceLocationName.Value;
|
||||
return baseName;
|
||||
nameIdentifier = type.ForceLocationName;
|
||||
DisplayName = TextManager.Get(nameIdentifier).Fallback(nameIdentifier.Value);
|
||||
return;
|
||||
}
|
||||
nameIdentifier = type.GetRandomNameId(rand, existingLocations);
|
||||
if (nameIdentifier.IsEmpty)
|
||||
{
|
||||
rawName = type.GetRandomRawName(rand, existingLocations);
|
||||
if (rawName.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to generate a name for a location of the type {type.Identifier}. No names found in localization files or the .txt files.");
|
||||
rawName = "none";
|
||||
}
|
||||
nameIdentifier = rawName.ToIdentifier();
|
||||
DisplayName = rawName;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type.NameFormats == null || !type.NameFormats.Any())
|
||||
{
|
||||
DisplayName = TextManager.Get(nameIdentifier).Fallback(nameIdentifier.Value);
|
||||
return;
|
||||
}
|
||||
nameFormatIndex = rand.Next() % type.NameFormats.Count;
|
||||
DisplayName = GetName(Type, nameFormatIndex, nameIdentifier);
|
||||
}
|
||||
baseName = type.GetRandomName(rand, existingLocations);
|
||||
if (type.NameFormats == null || !type.NameFormats.Any()) { return baseName; }
|
||||
nameFormatIndex = rand.Next() % type.NameFormats.Count;
|
||||
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
|
||||
}
|
||||
|
||||
public void ForceName(string name)
|
||||
private static LocalizedString GetName(LocationType type, int nameFormatIndex, Identifier nameId)
|
||||
{
|
||||
baseName = Name = name;
|
||||
if (type?.NameFormats == null || !type.NameFormats.Any())
|
||||
{
|
||||
return TextManager.Get(nameId);
|
||||
}
|
||||
return type.NameFormats[nameFormatIndex % type.NameFormats.Count].Replace("[name]", TextManager.Get(nameId).Value);
|
||||
}
|
||||
|
||||
public void ForceName(Identifier nameId)
|
||||
{
|
||||
rawName = string.Empty;
|
||||
nameIdentifier = nameId;
|
||||
DisplayName = TextManager.Get(nameId).Fallback(nameId.Value);
|
||||
}
|
||||
|
||||
public void LoadStores(XElement locationElement)
|
||||
@@ -1117,7 +1185,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
string msg = $"Error loading store info for \"{identifier}\" at location {Name} of type \"{Type.Identifier}\": duplicate identifier.";
|
||||
string msg = $"Error loading store info for \"{identifier}\" at location {DisplayName} of type \"{Type.Identifier}\": duplicate identifier.";
|
||||
DebugConsole.ThrowError(msg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Location.LoadStore:DuplicateStoreInfo", GameAnalyticsManager.ErrorSeverity.Error, msg);
|
||||
continue;
|
||||
@@ -1125,7 +1193,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
string msg = $"Error loading store info for \"{identifier}\" at location {Name} of type \"{Type.Identifier}\": location shouldn't contain a store with this identifier.";
|
||||
string msg = $"Error loading store info for \"{identifier}\" at location {DisplayName} of type \"{Type.Identifier}\": location shouldn't contain a store with this identifier.";
|
||||
DebugConsole.ThrowError(msg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Location.LoadStore:IncorrectStoreIdentifier", GameAnalyticsManager.ErrorSeverity.Error, msg);
|
||||
continue;
|
||||
@@ -1436,8 +1504,9 @@ namespace Barotrauma
|
||||
var locationElement = new XElement("location",
|
||||
new XAttribute("type", Type.Identifier),
|
||||
new XAttribute("originaltype", (Type ?? OriginalType).Identifier),
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
/*not used currently (we load the nameIdentifier instead),
|
||||
* but could make sense to include still for backwards compatibility reasons*/
|
||||
new XAttribute("name", DisplayName),
|
||||
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
@@ -1447,6 +1516,16 @@ namespace Barotrauma
|
||||
new XAttribute(nameof(TurnsInRadiation).ToLower(), TurnsInRadiation),
|
||||
new XAttribute("stepssincespecialsupdated", StepsSinceSpecialsUpdated));
|
||||
|
||||
if (!rawName.IsNullOrEmpty())
|
||||
{
|
||||
locationElement.Add(new XAttribute(nameof(rawName), rawName));
|
||||
}
|
||||
else
|
||||
{
|
||||
locationElement.Add(new XAttribute(nameof(nameIdentifier), nameIdentifier));
|
||||
locationElement.Add(new XAttribute(nameof(nameFormatIndex), nameFormatIndex));
|
||||
}
|
||||
|
||||
if (Faction != null)
|
||||
{
|
||||
locationElement.Add(new XAttribute("faction", Faction.Prefab.Identifier));
|
||||
@@ -1483,7 +1562,7 @@ namespace Barotrauma
|
||||
changeElement.Add(new XAttribute("index", index));
|
||||
if (index == -1)
|
||||
{
|
||||
DebugConsole.AddWarning($"Invalid location type change in the location \"{Name}\". Unknown type change ({PendingLocationTypeChange.Value.typeChange.ChangeToType}).");
|
||||
DebugConsole.AddWarning($"Invalid location type change in the location \"{DisplayName}\". Unknown type change ({PendingLocationTypeChange.Value.typeChange.ChangeToType}).");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,7 +14,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<LocationType> Prefabs = new PrefabCollection<LocationType>();
|
||||
|
||||
private readonly ImmutableArray<string> names;
|
||||
private readonly ImmutableArray<string> rawNames;
|
||||
private readonly ImmutableArray<Sprite> portraits;
|
||||
|
||||
//<name, commonness>
|
||||
@@ -26,7 +27,7 @@ namespace Barotrauma
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly LocalizedString ForceLocationName;
|
||||
public readonly Identifier ForceLocationName;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
@@ -54,12 +55,20 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly ImmutableArray<Identifier>? nameIdentifiers = null;
|
||||
|
||||
private LanguageIdentifier nameFormatLanguage;
|
||||
|
||||
private ImmutableArray<string>? nameFormats = null;
|
||||
public IReadOnlyList<string> NameFormats
|
||||
{
|
||||
get
|
||||
{
|
||||
nameFormats ??= TextManager.GetAll($"LocationNameFormat.{Identifier}").ToImmutableArray();
|
||||
if (nameFormats == null || GameSettings.CurrentConfig.Language != nameFormatLanguage)
|
||||
{
|
||||
nameFormats = TextManager.GetAll($"LocationNameFormat.{Identifier}").ToImmutableArray();
|
||||
nameFormatLanguage = GameSettings.CurrentConfig.Language;
|
||||
}
|
||||
return nameFormats;
|
||||
}
|
||||
}
|
||||
@@ -143,29 +152,37 @@ namespace Barotrauma
|
||||
|
||||
if (element.GetAttribute("name") != null)
|
||||
{
|
||||
ForceLocationName = TextManager.Get(element.GetAttributeString("name", string.Empty));
|
||||
ForceLocationName = element.GetAttributeIdentifier("name", string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] rawNamePaths = element.GetAttributeStringArray("namefile", new string[] { "Content/Map/locationNames.txt" });
|
||||
var names = new List<string>();
|
||||
foreach (string rawPath in rawNamePaths)
|
||||
//backwards compatibility for location names defined in a text file
|
||||
string[] rawNamePaths = element.GetAttributeStringArray("namefile", Array.Empty<string>());
|
||||
if (rawNamePaths.Any())
|
||||
{
|
||||
try
|
||||
foreach (string rawPath in rawNamePaths)
|
||||
{
|
||||
var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
|
||||
names.AddRange(File.ReadAllLines(path.Value).ToList());
|
||||
try
|
||||
{
|
||||
var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
|
||||
names.AddRange(File.ReadAllLines(path.Value).ToList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
if (!names.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
|
||||
names.Add("ERROR: No names found");
|
||||
}
|
||||
this.rawNames = names.ToImmutableArray();
|
||||
}
|
||||
if (!names.Any())
|
||||
else
|
||||
{
|
||||
names.Add("ERROR: No names found");
|
||||
nameIdentifiers = element.GetAttributeIdentifierArray("nameidentifiers", new Identifier[] { Identifier }).ToImmutableArray();
|
||||
}
|
||||
this.names = names.ToImmutableArray();
|
||||
}
|
||||
|
||||
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
|
||||
@@ -259,17 +276,64 @@ namespace Barotrauma
|
||||
return portraits[Math.Abs(randomSeed) % portraits.Length];
|
||||
}
|
||||
|
||||
public string GetRandomName(Random rand, IEnumerable<Location> existingLocations)
|
||||
public Identifier GetRandomNameId(Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (nameIdentifiers == null)
|
||||
{
|
||||
return Identifier.Empty;
|
||||
}
|
||||
List<Identifier> nameIds = new List<Identifier>();
|
||||
foreach (var nameId in nameIdentifiers)
|
||||
{
|
||||
int index = 0;
|
||||
while (true)
|
||||
{
|
||||
Identifier tag = $"LocationName.{nameId}.{index}".ToIdentifier();
|
||||
if (TextManager.ContainsTag(tag, TextManager.DefaultLanguage))
|
||||
{
|
||||
nameIds.Add(tag);
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find any location names for the location type {Identifier}. Name identifier: {nameId}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nameIds.None())
|
||||
{
|
||||
return Identifier.Empty;
|
||||
}
|
||||
if (existingLocations != null)
|
||||
{
|
||||
var unusedNames = names.Where(name => !existingLocations.Any(l => l.BaseName == name)).ToList();
|
||||
var unusedNameIds = nameIds.FindAll(nameId => existingLocations.None(l => l.NameIdentifier == nameId));
|
||||
if (unusedNameIds.Count > 0)
|
||||
{
|
||||
return unusedNameIds[rand.Next() % unusedNameIds.Count];
|
||||
}
|
||||
}
|
||||
return nameIds[rand.Next() % nameIds.Count];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For backwards compatibility. Chooses a random name from the names defined in the .txt name files (<see cref="rawNamePaths"/>).
|
||||
/// </summary>
|
||||
public string GetRandomRawName(Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (rawNames == null || rawNames.None()) { return string.Empty; }
|
||||
if (existingLocations != null)
|
||||
{
|
||||
var unusedNames = rawNames.Where(name => !existingLocations.Any(l => l.DisplayName.Value == name)).ToList();
|
||||
if (unusedNames.Count > 0)
|
||||
{
|
||||
return unusedNames[rand.Next() % unusedNames.Count];
|
||||
}
|
||||
}
|
||||
return names[rand.Next() % names.Length];
|
||||
return rawNames[rand.Next() % rawNames.Length];
|
||||
}
|
||||
|
||||
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false, Func<LocationType, bool> predicate = null)
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly bool RequireHuntingGrounds;
|
||||
|
||||
public Requirement(XElement element, LocationTypeChange change)
|
||||
public Requirement(ContentXElement element, LocationTypeChange change)
|
||||
{
|
||||
RequiredLocations = element.GetAttributeIdentifierArray("requiredlocations", element.GetAttributeIdentifierArray("requiredadjacentlocations", Array.Empty<Identifier>())).ToImmutableArray();
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 0);
|
||||
@@ -80,13 +80,15 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{change.CurrentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.");
|
||||
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.",
|
||||
element.ContentPackage);
|
||||
}
|
||||
if (Probability >= 1.0f)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{change.CurrentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the base probability is already 100%");
|
||||
"Probability is configured to increase when near some other type of location, but the base probability is already 100%",
|
||||
element.ContentPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +175,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly Point RequiredDurationRange;
|
||||
|
||||
public LocationTypeChange(Identifier currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
|
||||
public LocationTypeChange(Identifier currentType, ContentXElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
|
||||
{
|
||||
CurrentType = currentType;
|
||||
ChangeToType = element.GetAttributeIdentifier("type", element.GetAttributeIdentifier("to", ""));
|
||||
@@ -190,13 +192,13 @@ namespace Barotrauma
|
||||
CooldownAfterChange = Math.Max(element.GetAttributeInt("cooldownafterchange", 0), 0);
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredlocations") != null)
|
||||
if (element.GetAttribute("requiredlocations") != null)
|
||||
{
|
||||
Requirements.Add(new Requirement(element, this));
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredduration") != null)
|
||||
if (element.GetAttribute("requiredduration") != null)
|
||||
{
|
||||
RequiredDurationRange = new Point(element.GetAttributeInt("requiredduration", 0));
|
||||
}
|
||||
|
||||
@@ -262,9 +262,9 @@ namespace Barotrauma
|
||||
foreach (var endLocation in EndLocations)
|
||||
{
|
||||
if (endLocation.Type?.ForceLocationName != null &&
|
||||
!endLocation.Type.ForceLocationName.IsNullOrEmpty())
|
||||
!endLocation.Type.ForceLocationName.IsEmpty)
|
||||
{
|
||||
endLocation.ForceName(endLocation.Type.ForceLocationName.Value);
|
||||
endLocation.ForceName(endLocation.Type.ForceLocationName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,10 +1005,10 @@ namespace Barotrauma
|
||||
CurrentLocation.CreateStores();
|
||||
OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation));
|
||||
|
||||
if (GameMain.GameSession is { Campaign: { CampaignMetadata: { } metadata } })
|
||||
if (GameMain.GameSession is { Campaign.CampaignMetadata: { } metadata })
|
||||
{
|
||||
metadata.SetValue("campaign.location.id".ToIdentifier(), CurrentLocationIndex);
|
||||
metadata.SetValue("campaign.location.name".ToIdentifier(), CurrentLocation.Name);
|
||||
metadata.SetValue("campaign.location.name".ToIdentifier(), CurrentLocation.NameIdentifier.Value);
|
||||
metadata.SetValue("campaign.location.biome".ToIdentifier(), CurrentLocation.Biome?.Identifier ?? "null".ToIdentifier());
|
||||
metadata.SetValue("campaign.location.type".ToIdentifier(), CurrentLocation.Type?.Identifier ?? "null".ToIdentifier());
|
||||
}
|
||||
@@ -1077,7 +1077,7 @@ namespace Barotrauma
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
string errorMsg =
|
||||
$"A locked connection was selected ({SelectedConnection.Locations[0].Name} -> {SelectedConnection.Locations[1].Name}." +
|
||||
$"A locked connection was selected ({SelectedConnection.Locations[0].DisplayName} -> {SelectedConnection.Locations[1].DisplayName}." +
|
||||
$" Current location: {CurrentLocation}, current display location: {currentDisplayLocation}).\n"
|
||||
+ Environment.StackTrace.CleanupStackTrace();
|
||||
GameAnalyticsManager.AddErrorEventOnce("MapSelectLocation:LockedConnectionSelected", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
@@ -1093,7 +1093,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!Locations.Contains(location))
|
||||
{
|
||||
string errorMsg = "Failed to select a location. " + (location?.Name ?? "null") + " not found in the map.";
|
||||
string errorMsg = $"Failed to select a location. {location?.DisplayName ?? "null"} not found in the map.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
@@ -1301,11 +1301,11 @@ namespace Barotrauma
|
||||
|
||||
private bool ChangeLocationType(CampaignMode campaign, Location location, LocationTypeChange change)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
LocalizedString prevName = location.DisplayName;
|
||||
|
||||
if (!LocationType.Prefabs.TryGet(change.ChangeToType, out var newType))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found.");
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.DisplayName}\". Location type \"{change.ChangeToType}\" not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1372,7 +1372,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change);
|
||||
partial void ChangeLocationTypeProjSpecific(Location location, LocalizedString prevName, LocationTypeChange change);
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
@@ -1498,7 +1498,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
string prevLocationName = location.Name;
|
||||
LocalizedString prevLocationName = location.DisplayName;
|
||||
LocationType prevLocationType = location.Type;
|
||||
LocationType newLocationType = LocationType.Prefabs.Find(lt => lt.Identifier == locationType) ?? LocationType.Prefabs.First();
|
||||
location.ChangeType(campaign, newLocationType);
|
||||
@@ -1619,7 +1619,7 @@ namespace Barotrauma
|
||||
//this should not be possible, you can't enter non-outpost locations (= natural formations)
|
||||
if (CurrentLocation != null && !CurrentLocation.Type.HasOutpost && SelectedConnection == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading campaign map state. Submarine in a location with no outpost ({CurrentLocation.Name}). Loading the first adjacent connection...");
|
||||
DebugConsole.AddWarning($"Error while loading campaign map state. Submarine in a location with no outpost ({CurrentLocation.DisplayName}). Loading the first adjacent connection...");
|
||||
SelectLocation(CurrentLocation.Connections[0].OtherLocation(CurrentLocation));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,15 +580,10 @@ namespace Barotrauma
|
||||
base.Remove();
|
||||
|
||||
MapEntityList.Remove(this);
|
||||
|
||||
#if CLIENT
|
||||
Submarine.ForceRemoveFromVisibleEntities(this);
|
||||
if (SelectedList.Contains(this))
|
||||
{
|
||||
SelectedList = SelectedList.Where(e => e != this).ToHashSet();
|
||||
}
|
||||
SelectedList.Remove(this);
|
||||
#endif
|
||||
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.Remove();
|
||||
@@ -716,6 +711,9 @@ namespace Barotrauma
|
||||
Move(-relative * 2.0f);
|
||||
}
|
||||
|
||||
public virtual Quad2D GetTransformedQuad()
|
||||
=> Quad2D.FromSubmarineRectangle(rect);
|
||||
|
||||
public static List<MapEntity> LoadAll(Submarine submarine, XElement parentElement, string filePath, int idOffset)
|
||||
{
|
||||
IdRemap idRemap = new IdRemap(parentElement, idOffset);
|
||||
|
||||
+47
-17
@@ -3,13 +3,11 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BeaconStationInfo : ISerializableEntity
|
||||
abstract class ExtraSubmarineInfo : ISerializableEntity
|
||||
{
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDamagedWalls { get; set; }
|
||||
public string Name { get; protected set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDisconnectedWires { get; set; }
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; protected set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MinLevelDifficulty { get; set; }
|
||||
@@ -17,26 +15,19 @@ namespace Barotrauma
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MaxLevelDifficulty { get; set; }
|
||||
|
||||
[Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable]
|
||||
public Level.PlacementType Placement { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element)
|
||||
public ExtraSubmarineInfo(SubmarineInfo submarineInfo, XElement element)
|
||||
{
|
||||
Name = $"BeaconStationInfo ({submarineInfo.Name})";
|
||||
Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo)
|
||||
public ExtraSubmarineInfo(SubmarineInfo submarineInfo)
|
||||
{
|
||||
Name = $"BeaconStationInfo ({submarineInfo.Name})";
|
||||
Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this);
|
||||
}
|
||||
|
||||
public BeaconStationInfo(BeaconStationInfo original)
|
||||
public ExtraSubmarineInfo(ExtraSubmarineInfo original)
|
||||
{
|
||||
Name = original.Name;
|
||||
SerializableProperties = new Dictionary<Identifier, SerializableProperty>();
|
||||
@@ -55,4 +46,43 @@ namespace Barotrauma
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
|
||||
class BeaconStationInfo : ExtraSubmarineInfo
|
||||
{
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDamagedWalls { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDisconnectedWires { get; set; }
|
||||
|
||||
[Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable]
|
||||
public Level.PlacementType Placement { get; set; }
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element)
|
||||
{
|
||||
Name = $"{nameof(BeaconStationInfo)} ({submarineInfo.Name})";
|
||||
}
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo) : base(submarineInfo)
|
||||
{
|
||||
Name = $"{nameof(BeaconStationInfo)} ({submarineInfo.Name})";
|
||||
}
|
||||
|
||||
public BeaconStationInfo(BeaconStationInfo original) : base(original) { }
|
||||
}
|
||||
|
||||
class WreckInfo : ExtraSubmarineInfo
|
||||
{
|
||||
public WreckInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element)
|
||||
{
|
||||
Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})";
|
||||
}
|
||||
|
||||
public WreckInfo(SubmarineInfo submarineInfo) : base(submarineInfo)
|
||||
{
|
||||
Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})";
|
||||
}
|
||||
|
||||
public WreckInfo(WreckInfo original) : base(original) { }
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.");
|
||||
DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.", contentPackage: element.ContentPackage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsHorizontal { get; private set; }
|
||||
public bool IsHorizontal { get; }
|
||||
|
||||
public int SectionCount
|
||||
{
|
||||
@@ -240,6 +240,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected float rotationRad = 0f;
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, DecimalCount = 3, ForceShowPlusMinusButtons = true, ValueStep = 0.1f), Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||
public float Rotation
|
||||
{
|
||||
get => MathHelper.ToDegrees(rotationRad);
|
||||
set
|
||||
{
|
||||
rotationRad = MathHelper.WrapAngle(MathHelper.ToRadians(value));
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
CreateStairBodies();
|
||||
}
|
||||
else if (Prefab.Body)
|
||||
{
|
||||
CreateSections();
|
||||
UpdateSections();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Vector2 textureScale = Vector2.One;
|
||||
|
||||
@@ -336,9 +355,18 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
float rotation = MathHelper.ToRadians(Prefab.BodyRotation);
|
||||
if (FlippedX) rotation = -MathHelper.Pi - rotation;
|
||||
if (FlippedY) rotation = -rotation;
|
||||
float rotation = MathHelper.ToRadians(Prefab.BodyRotation) + this.rotationRad;
|
||||
if (IsHorizontal)
|
||||
{
|
||||
if (FlippedX) { rotation = -MathHelper.Pi - rotation; }
|
||||
if (FlippedY) { rotation = -rotation; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FlippedX) { rotation = -rotation; }
|
||||
if (FlippedY) { rotation = -MathHelper.Pi -rotation; }
|
||||
}
|
||||
rotation = MathHelper.WrapAngle(rotation);
|
||||
return rotation;
|
||||
}
|
||||
}
|
||||
@@ -350,6 +378,10 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
Vector2 bodyOffset = Prefab.BodyOffset;
|
||||
if (rotationRad != 0f)
|
||||
{
|
||||
bodyOffset = MathUtils.RotatePoint(bodyOffset, -rotationRad);
|
||||
}
|
||||
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
|
||||
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
|
||||
return bodyOffset;
|
||||
@@ -567,9 +599,14 @@ namespace Barotrauma
|
||||
|
||||
Body newBody = GameMain.World.CreateRectangle(bodyWidth, bodyHeight, 1.5f);
|
||||
|
||||
var rotationWithFlip = FlippedX ^ FlippedY ? -rotationRad : rotationRad;
|
||||
|
||||
newBody.BodyType = BodyType.Static;
|
||||
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + stairHeight / 2.0f);
|
||||
newBody.Rotation = (StairDirection == Direction.Right) ? stairAngle : -stairAngle;
|
||||
Vector2 stairRectHeightDiff = new Vector2(0f, stairHeight / 2.0f - rect.Height / 2.0f);
|
||||
stairRectHeightDiff = MathUtils.RotatePoint(stairRectHeightDiff, -rotationWithFlip);
|
||||
if (FlippedY) { stairRectHeightDiff = -stairRectHeightDiff; }
|
||||
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height / 2.0f) + stairRectHeightDiff;
|
||||
newBody.Rotation = ((StairDirection == Direction.Right) ? stairAngle : -stairAngle) - rotationWithFlip;
|
||||
newBody.CollisionCategories = Physics.CollisionStairs;
|
||||
newBody.Friction = 0.8f;
|
||||
newBody.UserData = this;
|
||||
@@ -696,16 +733,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2[] CalculateExtremes(Rectangle sectionRect)
|
||||
{
|
||||
Vector2[] corners = new Vector2[4];
|
||||
corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height);
|
||||
corners[1] = new Vector2(sectionRect.X, sectionRect.Y);
|
||||
corners[2] = new Vector2(sectionRect.Right, sectionRect.Y);
|
||||
corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height);
|
||||
|
||||
return corners;
|
||||
}
|
||||
public override Quad2D GetTransformedQuad()
|
||||
=> Quad2D.FromSubmarineRectangle(rect).Rotated(
|
||||
FlippedX != FlippedY
|
||||
? rotationRad
|
||||
: -rotationRad);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there's a structure items can be attached to at the given position and returns it.
|
||||
@@ -727,8 +759,6 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsMouseOn(Vector2 position)
|
||||
{
|
||||
if (!base.IsMouseOn(position)) { return false; }
|
||||
|
||||
if (StairDirection == Direction.None)
|
||||
{
|
||||
Vector2 rectSize = rect.Size.ToVector2();
|
||||
@@ -745,14 +775,19 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(
|
||||
position,
|
||||
WorldRect.Location.ToVector2() + WorldRect.Size.ToVector2().FlipY() * 0.5f,
|
||||
BodyRotation);
|
||||
|
||||
if (!Submarine.RectContains(WorldRect, position)) { return false; }
|
||||
if (StairDirection == Direction.Left)
|
||||
{
|
||||
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), position) < 1600.0f;
|
||||
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), transformedMousePos) < 1600.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), position) < 1600.0f;
|
||||
return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), transformedMousePos) < 1600.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -883,6 +918,12 @@ namespace Barotrauma
|
||||
return Sections[sectionIndex].damage >= MaxHealth * LeakThreshold;
|
||||
}
|
||||
|
||||
public bool SectionIsLeakingFromOutside(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; }
|
||||
return SectionIsLeaking(sectionIndex) && !Sections[sectionIndex].gap.IsRoomToRoom;
|
||||
}
|
||||
|
||||
public int SectionLength(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return 0;
|
||||
@@ -934,11 +975,19 @@ namespace Barotrauma
|
||||
for (int i = 1; i <= particleAmount; i++)
|
||||
{
|
||||
var worldRect = section.WorldRect;
|
||||
var directionUnitX = MathUtils.RotatedUnitXRadians(BodyRotation);
|
||||
var directionUnitY = directionUnitX.YX().FlipX();
|
||||
Vector2 particlePos = new Vector2(
|
||||
Rand.Range(worldRect.X, worldRect.Right + 1),
|
||||
Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y + 1));
|
||||
Rand.Range(0, worldRect.Width + 1),
|
||||
Rand.Range(-worldRect.Height, 1));
|
||||
particlePos -= worldRect.Size.ToVector2().FlipY() * 0.5f;
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle, particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
|
||||
var particlePosFinal = SectionPosition(sectionIndex, world: true);
|
||||
particlePosFinal += particlePos.X * directionUnitX + particlePos.Y * directionUnitY;
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle,
|
||||
position: particlePosFinal,
|
||||
velocity: Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
|
||||
if (particle == null) break;
|
||||
}
|
||||
}
|
||||
@@ -960,14 +1009,23 @@ namespace Barotrauma
|
||||
|
||||
//if the sub has been flipped horizontally, the first section may be smaller than wallSectionSize
|
||||
//and we need to adjust the position accordingly
|
||||
if (Sections[0].rect.Width < WallSectionSize)
|
||||
if (IsHorizontal)
|
||||
{
|
||||
displayPos.X += WallSectionSize - Sections[0].rect.Width;
|
||||
if (Sections[0].rect.Width < WallSectionSize)
|
||||
{
|
||||
displayPos += DirectionUnit * (WallSectionSize - Sections[0].rect.Width);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Sections[0].rect.Height < WallSectionSize)
|
||||
{
|
||||
displayPos += DirectionUnit * (WallSectionSize - Sections[0].rect.Height);
|
||||
}
|
||||
}
|
||||
|
||||
int index = IsHorizontal ?
|
||||
(int)Math.Floor((displayPos.X - rect.X) / WallSectionSize) :
|
||||
(int)Math.Floor((rect.Y - displayPos.Y) / WallSectionSize);
|
||||
var leftmostPos = Position - DirectionUnit * (IsHorizontal ? Rect.Width : Rect.Height) * 0.5f;
|
||||
int index = (int)Math.Floor(Vector2.Dot(DirectionUnit, displayPos - leftmostPos) / WallSectionSize);
|
||||
|
||||
if (clamp)
|
||||
{
|
||||
@@ -987,6 +1045,17 @@ namespace Barotrauma
|
||||
return Sections[sectionIndex].damage;
|
||||
}
|
||||
|
||||
protected Vector2 DirectionUnit
|
||||
{
|
||||
get
|
||||
{
|
||||
var rotation = IsHorizontal ? -BodyRotation : -MathHelper.PiOver2 - BodyRotation;
|
||||
if (IsHorizontal && FlippedX) { rotation += MathF.PI; }
|
||||
if (!IsHorizontal && FlippedY) { rotation += MathF.PI; }
|
||||
return MathUtils.RotatedUnitXRadians(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 SectionPosition(int sectionIndex, bool world = false)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length)
|
||||
@@ -994,7 +1063,7 @@ namespace Barotrauma
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
if (Prefab.BodyRotation == 0.0f)
|
||||
if (MathUtils.NearlyEqual(BodyRotation, 0f))
|
||||
{
|
||||
Vector2 sectionPos = new Vector2(
|
||||
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
|
||||
@@ -1017,15 +1086,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight;
|
||||
}
|
||||
if (FlippedX)
|
||||
{
|
||||
diffFromCenter = -diffFromCenter;
|
||||
}
|
||||
|
||||
Vector2 sectionPos = Position + new Vector2(
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
|
||||
Vector2 sectionPos = Position + DirectionUnit * diffFromCenter;
|
||||
|
||||
if (world && Submarine != null)
|
||||
{
|
||||
@@ -1035,13 +1099,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = false)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode) { return new AttackResult(0.0f, null); }
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible) { return new AttackResult(0.0f, null); }
|
||||
|
||||
Vector2 transformedPos = worldPosition;
|
||||
if (Submarine != null) transformedPos -= Submarine.Position;
|
||||
if (Submarine != null) { transformedPos -= Submarine.Position; }
|
||||
|
||||
if (!MathUtils.NearlyEqual(BodyRotation, 0f))
|
||||
{
|
||||
var center = Rect.Location.ToVector2() + Rect.Size.ToVector2().FlipY() * 0.5f;
|
||||
var rotation = BodyRotation;
|
||||
if (IsHorizontal && FlippedX) { rotation += MathF.PI; }
|
||||
if (!IsHorizontal && FlippedY) { rotation += MathF.PI; }
|
||||
transformedPos = MathUtils.RotatePointAroundTarget(transformedPos, center, rotation);
|
||||
}
|
||||
|
||||
float damageAmount = 0.0f;
|
||||
for (int i = 0; i < SectionCount; i++)
|
||||
@@ -1143,6 +1216,7 @@ namespace Barotrauma
|
||||
gapRect.Y = (gapRect.Y - gapRect.Height / 2) + (int)(BodyHeight / 2 + BodyOffset.Y * scale);
|
||||
gapRect.Height = (int)BodyHeight;
|
||||
}
|
||||
if (FlippedX) { diffFromCenter = -diffFromCenter; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1153,8 +1227,8 @@ namespace Barotrauma
|
||||
gapRect.Width = (int)BodyWidth;
|
||||
}
|
||||
if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); }
|
||||
if (FlippedY) { diffFromCenter = -diffFromCenter; }
|
||||
}
|
||||
if (FlippedX) { diffFromCenter = -diffFromCenter; }
|
||||
|
||||
if (Math.Abs(BodyRotation) > 0.01f)
|
||||
{
|
||||
@@ -1170,14 +1244,26 @@ namespace Barotrauma
|
||||
gapRect.Width += 20;
|
||||
gapRect.Height += 20;
|
||||
|
||||
bool horizontalGap = !IsHorizontal;
|
||||
bool rotatedEnoughToChangeOrientation = (MathUtils.WrapAngleTwoPi(rotationRad - MathHelper.PiOver4) % MathHelper.Pi < MathHelper.PiOver2);
|
||||
if (rotatedEnoughToChangeOrientation)
|
||||
{
|
||||
var center = gapRect.Location + gapRect.Size.FlipY() / new Point(2);
|
||||
var topLeft = gapRect.Location;
|
||||
var diff = topLeft - center;
|
||||
diff = diff.FlipY().YX().FlipY();
|
||||
var newTopLeft = diff + center;
|
||||
gapRect = new Rectangle(newTopLeft, gapRect.Size.YX());
|
||||
}
|
||||
bool horizontalGap = rotatedEnoughToChangeOrientation
|
||||
? IsHorizontal
|
||||
: !IsHorizontal;
|
||||
bool diagonalGap = false;
|
||||
if (Prefab.BodyRotation != 0.0f)
|
||||
if (!MathUtils.NearlyEqual(BodyRotation, 0f))
|
||||
{
|
||||
//rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80)
|
||||
float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2;
|
||||
//diagonal if 30 < angle < 60
|
||||
diagonalGap = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
|
||||
diagonalGap = sectorizedRotation is > MathHelper.Pi / 6 and < MathHelper.Pi / 3;
|
||||
//gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical
|
||||
if (diagonalGap)
|
||||
{
|
||||
@@ -1230,8 +1316,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (damageDiff < 0.0f)
|
||||
{
|
||||
attacker.Info?.IncreaseSkillLevel("mechanical".ToIdentifier(),
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f));
|
||||
attacker.Info?.ApplySkillGain(Barotrauma.Tags.MechanicalSkill,
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1337,7 +1423,7 @@ namespace Barotrauma
|
||||
{
|
||||
hasHoles = true;
|
||||
|
||||
if (!mergedSections.Any()) continue;
|
||||
if (!mergedSections.Any()) { continue; }
|
||||
var mergedRect = GenerateMergedRect(mergedSections);
|
||||
mergedSections.Clear();
|
||||
CreateRectBody(mergedRect, createConvexHull: true);
|
||||
@@ -1373,18 +1459,17 @@ namespace Barotrauma
|
||||
diffFromCenter = (rect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth;
|
||||
if (BodyWidth > 0.0f) rect.Width = Math.Max((int)Math.Round(BodyWidth * (rect.Width / (float)this.rect.Width)), 1);
|
||||
if (BodyHeight > 0.0f) rect.Height = (int)BodyHeight;
|
||||
if (FlippedX) { diffFromCenter = -diffFromCenter; }
|
||||
}
|
||||
else
|
||||
{
|
||||
diffFromCenter = ((rect.Y - rect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight;
|
||||
if (BodyWidth > 0.0f) rect.Width = (int)BodyWidth;
|
||||
if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1);
|
||||
if (FlippedY) { diffFromCenter = -diffFromCenter; }
|
||||
}
|
||||
if (FlippedX) { diffFromCenter = -diffFromCenter; }
|
||||
|
||||
Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale;
|
||||
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
|
||||
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
|
||||
Vector2 bodyOffset = ConvertUnits.ToSimUnits(BodyOffset) * scale;
|
||||
|
||||
Body newBody = GameMain.World.CreateRectangle(
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
@@ -1398,7 +1483,7 @@ namespace Barotrauma
|
||||
newBody.UserData = this;
|
||||
|
||||
Vector2 structureCenter = ConvertUnits.ToSimUnits(Position);
|
||||
if (BodyRotation != 0.0f)
|
||||
if (!MathUtils.NearlyEqual(BodyRotation, 0f))
|
||||
{
|
||||
Vector2 pos = structureCenter + bodyOffset + new Vector2(
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can items like signal components be attached on this structure? Should be enabled on structures like decorative background walls.")]
|
||||
public bool AllowAttachItems { get; private set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Can the structure be rotated in the submarine editor?")]
|
||||
public bool AllowRotatingInEditor { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float MinHealth { get; private set; }
|
||||
|
||||
@@ -297,14 +300,16 @@ namespace Barotrauma
|
||||
if (Identifier == Identifier.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
"Structure prefab \"" + Name + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.");
|
||||
"Structure prefab \"" + Name.Value + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.",
|
||||
contentPackage: ContentPackage);
|
||||
}
|
||||
#if DEBUG
|
||||
if (!Category.HasFlag(MapEntityCategory.Legacy) && !HideInMenus)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(OriginalName))
|
||||
{
|
||||
DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.");
|
||||
DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.",
|
||||
ContentPackage);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -505,51 +505,58 @@ namespace Barotrauma
|
||||
minWidth += padding;
|
||||
minHeight += padding;
|
||||
|
||||
Vector2 limits = GetHorizontalLimits(spawnPos, minWidth, minHeight, 0);
|
||||
if (verticalMoveDir != 0)
|
||||
int iterations = 0;
|
||||
const int maxIterations = 5;
|
||||
do
|
||||
{
|
||||
verticalMoveDir = Math.Sign(verticalMoveDir);
|
||||
//do a raycast towards the top/bottom of the level depending on direction
|
||||
Vector2 potentialPos = new Vector2(spawnPos.X, verticalMoveDir > 0 ? Level.Loaded.Size.Y : 0);
|
||||
|
||||
//3 raycasts (left, middle and right side of the sub, so we don't accidentally raycast up a passage too narrow for the sub)
|
||||
for (int x = -1; x <= 1; x++)
|
||||
Vector2 potentialPos = spawnPos;
|
||||
if (verticalMoveDir != 0)
|
||||
{
|
||||
Vector2 xOffset = Vector2.UnitX * minWidth / 2 * x;
|
||||
if (PickBody(
|
||||
ConvertUnits.ToSimUnits(spawnPos + xOffset),
|
||||
ConvertUnits.ToSimUnits(potentialPos + xOffset),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
|
||||
verticalMoveDir = Math.Sign(verticalMoveDir);
|
||||
//do a raycast towards the top/bottom of the level depending on direction
|
||||
Vector2 rayEnd = new Vector2(potentialPos.X, verticalMoveDir > 0 ? Level.Loaded.Size.Y : 0);
|
||||
|
||||
Vector2 closestPickedPos = rayEnd;
|
||||
//multiple raycast across the width of the sub (so we don't accidentally raycast up a passage too narrow for the sub)
|
||||
for (float x = -1; x <= 1; x += 0.2f)
|
||||
{
|
||||
int offsetFromWall = 10 * -verticalMoveDir;
|
||||
//if the raycast hit a wall, attempt to place the spawnpos there
|
||||
if (verticalMoveDir > 0)
|
||||
Vector2 xOffset = Vector2.UnitX * minWidth / 2 * x;
|
||||
xOffset.X += subDockingPortOffset;
|
||||
if (PickBody(
|
||||
ConvertUnits.ToSimUnits(potentialPos + xOffset),
|
||||
ConvertUnits.ToSimUnits(rayEnd + xOffset),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
return f.UserData is not VoronoiCell { IsDestructible: true };
|
||||
}) != null)
|
||||
{
|
||||
potentialPos.Y = Math.Min(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall);
|
||||
}
|
||||
else
|
||||
{
|
||||
potentialPos.Y = Math.Max(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall);
|
||||
//if the raycast hit a wall, attempt to place the spawnpos there
|
||||
int offsetFromWall = 10 * -verticalMoveDir;
|
||||
float pickedPos = ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall;
|
||||
closestPickedPos.Y =
|
||||
verticalMoveDir > 0 ?
|
||||
Math.Min(closestPickedPos.Y, pickedPos) :
|
||||
Math.Max(closestPickedPos.Y, pickedPos);
|
||||
}
|
||||
}
|
||||
potentialPos.Y = closestPickedPos.Y;
|
||||
}
|
||||
|
||||
//step away from the top/bottom of the level, or from whatever wall the raycast hit,
|
||||
//until we found a spot where there's enough room to place the sub
|
||||
float dist = Math.Abs(potentialPos.Y - spawnPos.Y);
|
||||
for (float d = dist; d > 0; d -= 100.0f)
|
||||
Vector2 limits = GetHorizontalLimits(new Vector2(potentialPos.X, potentialPos.Y - (dockedBorders.Height * 0.5f * verticalMoveDir)),
|
||||
maxHorizontalMoveAmount: minWidth, minHeight, verticalMoveDir, padding);
|
||||
if (limits.Y - limits.X >= minWidth)
|
||||
{
|
||||
float y = spawnPos.Y + verticalMoveDir * d;
|
||||
limits = GetHorizontalLimits(new Vector2(spawnPos.X, y), minWidth, minHeight, verticalMoveDir);
|
||||
if (limits.Y - limits.X > minWidth)
|
||||
{
|
||||
spawnPos = new Vector2(spawnPos.X, y - (dockedBorders.Height * 0.5f * verticalMoveDir));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector2 newSpawnPos = new Vector2(spawnPos.X, potentialPos.Y - (dockedBorders.Height * 0.5f * verticalMoveDir));
|
||||
bool couldMoveInVerticalMoveDir = Math.Sign(newSpawnPos.Y - spawnPos.Y) == Math.Sign(verticalMoveDir);
|
||||
if (!couldMoveInVerticalMoveDir) { break; }
|
||||
spawnPos = ClampToHorizontalLimits(newSpawnPos, limits);
|
||||
}
|
||||
|
||||
static Vector2 GetHorizontalLimits(Vector2 spawnPos, float minWidth, float minHeight, int verticalMoveDir)
|
||||
iterations++;
|
||||
} while (iterations < maxIterations);
|
||||
|
||||
Vector2 GetHorizontalLimits(Vector2 spawnPos, float maxHorizontalMoveAmount, float minHeight, int verticalMoveDir, int padding)
|
||||
{
|
||||
Vector2 refPos = spawnPos - Vector2.UnitY * minHeight * 0.5f * Math.Sign(verticalMoveDir);
|
||||
|
||||
@@ -580,34 +587,44 @@ namespace Barotrauma
|
||||
if (Math.Abs(ruin.Area.Center.Y - refPos.Y) > (minHeight + ruin.Area.Height) * 0.5f) { continue; }
|
||||
if (ruin.Area.Center.X < refPos.X)
|
||||
{
|
||||
minX = Math.Max(minX, ruin.Area.Right + 100.0f);
|
||||
minX = Math.Max(minX, ruin.Area.Right + padding);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
|
||||
maxX = Math.Min(maxX, ruin.Area.X - padding);
|
||||
}
|
||||
}
|
||||
return new Vector2(Math.Max(minX, spawnPos.X - minWidth), Math.Min(maxX, spawnPos.X + minWidth));
|
||||
|
||||
minX += subDockingPortOffset;
|
||||
maxX += subDockingPortOffset;
|
||||
|
||||
return new Vector2(
|
||||
Math.Max(Math.Max(minX, spawnPos.X - maxHorizontalMoveAmount - padding), 0),
|
||||
Math.Min(Math.Min(maxX, spawnPos.X + maxHorizontalMoveAmount + padding), Level.Loaded.Size.X));
|
||||
}
|
||||
|
||||
if (limits.X < 0.0f && limits.Y > Level.Loaded.Size.X)
|
||||
Vector2 ClampToHorizontalLimits(Vector2 spawnPos, Vector2 limits)
|
||||
{
|
||||
//no walls found at either side, just use the initial spawnpos and hope for the best
|
||||
}
|
||||
else if (limits.X < 0)
|
||||
{
|
||||
//no wall found at the left side, spawn to the left from the right-side wall
|
||||
spawnPos.X = limits.Y - minWidth * 0.5f - 100.0f + subDockingPortOffset;
|
||||
}
|
||||
else if (limits.Y > Level.Loaded.Size.X)
|
||||
{
|
||||
//no wall found at right side, spawn to the right from the left-side wall
|
||||
spawnPos.X = limits.X + minWidth * 0.5f + 100.0f + subDockingPortOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
//walls found at both sides, use their midpoint
|
||||
spawnPos.X = (limits.X + limits.Y) / 2 + subDockingPortOffset;
|
||||
if (limits.X < 0.0f && limits.Y > Level.Loaded.Size.X)
|
||||
{
|
||||
//no walls found at either side, just use the initial spawnpos and hope for the best
|
||||
}
|
||||
else if (limits.X < 0)
|
||||
{
|
||||
//no wall found at the left side, spawn to the left from the right-side wall
|
||||
spawnPos.X = limits.Y - minWidth * 0.5f - 100.0f + subDockingPortOffset;
|
||||
}
|
||||
else if (limits.Y > Level.Loaded.Size.X)
|
||||
{
|
||||
//no wall found at right side, spawn to the right from the left-side wall
|
||||
spawnPos.X = limits.X + minWidth * 0.5f + 100.0f + subDockingPortOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
//walls found at both sides, use their midpoint
|
||||
spawnPos.X = (limits.X + limits.Y) / 2 + subDockingPortOffset;
|
||||
}
|
||||
return spawnPos;
|
||||
}
|
||||
|
||||
spawnPos.Y = MathHelper.Clamp(spawnPos.Y, dockedBorders.Height / 2 + 10, Level.Loaded.Size.Y - dockedBorders.Height / 2 - padding * 2);
|
||||
@@ -624,7 +641,7 @@ namespace Barotrauma
|
||||
|
||||
//math/physics stuff ----------------------------------------------------
|
||||
|
||||
public static Vector2 VectorToWorldGrid(Vector2 position, bool round = false)
|
||||
public static Vector2 VectorToWorldGrid(Vector2 position, Submarine sub = null, bool round = false)
|
||||
{
|
||||
if (round)
|
||||
{
|
||||
@@ -636,6 +653,12 @@ namespace Barotrauma
|
||||
position.X = MathF.Floor(position.X / GridSize.X) * GridSize.X;
|
||||
position.Y = MathF.Ceiling(position.Y / GridSize.Y) * GridSize.Y;
|
||||
}
|
||||
|
||||
if (sub != null)
|
||||
{
|
||||
position.X += sub.Position.X % GridSize.X;
|
||||
position.Y += sub.Position.Y % GridSize.Y;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
@@ -923,11 +946,17 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// check visibility between two points (in sim units)
|
||||
/// Check visibility between two points (in sim units).
|
||||
/// </summary>
|
||||
/// <returns>a physics body that was between the points (or null)</returns>
|
||||
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true, bool ignoreDisabledWalls = true, bool ignoreBranches = true)
|
||||
///
|
||||
|
||||
/// <param name="ignoreBranches">Should plants' branches be ignored?</param>
|
||||
/// <param name="blocksVisibilityPredicate">If the predicate returns false, the fixture is ignored even if it would normally block visibility.</param>
|
||||
/// <returns>A physics body that was between the points (or null)</returns>
|
||||
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true, bool ignoreDisabledWalls = true, bool ignoreBranches = true,
|
||||
Predicate<Fixture> blocksVisibilityPredicate = null)
|
||||
{
|
||||
Body closestBody = null;
|
||||
float closestFraction = 1.0f;
|
||||
@@ -962,7 +991,10 @@ namespace Barotrauma
|
||||
if (sectionIndex > -1 && structure.SectionBodyDisabled(sectionIndex)) { return -1; }
|
||||
}
|
||||
}
|
||||
|
||||
if (blocksVisibilityPredicate != null && !blocksVisibilityPredicate(fixture))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (fraction < closestFraction)
|
||||
{
|
||||
closestBody = fixture.Body;
|
||||
@@ -1840,6 +1872,7 @@ namespace Barotrauma
|
||||
FilePath = filePath,
|
||||
OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null,
|
||||
BeaconStationInfo = Info.BeaconStationInfo != null ? new BeaconStationInfo(Info.BeaconStationInfo) : null,
|
||||
WreckInfo = Info.WreckInfo != null ? new WreckInfo(Info.WreckInfo) : null,
|
||||
Name = Path.GetFileNameWithoutExtension(filePath)
|
||||
};
|
||||
#if CLIENT
|
||||
@@ -1878,12 +1911,11 @@ namespace Barotrauma
|
||||
Unloading = true;
|
||||
try
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
RoundSound.RemoveAllRoundSounds();
|
||||
GameMain.LightManager?.ClearLights();
|
||||
depthSortedDamageable.Clear();
|
||||
#endif
|
||||
|
||||
var _loaded = new List<Submarine>(loaded);
|
||||
foreach (Submarine sub in _loaded)
|
||||
{
|
||||
@@ -1918,9 +1950,11 @@ namespace Barotrauma
|
||||
|
||||
Ragdoll.RemoveAll();
|
||||
PhysicsBody.RemoveAll();
|
||||
StatusEffect.StopAll();
|
||||
GameMain.World = null;
|
||||
|
||||
Powered.Grids.Clear();
|
||||
Powered.ChangedConnections.Clear();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
@@ -1940,6 +1974,7 @@ namespace Barotrauma
|
||||
|
||||
outdoorNodes?.Clear();
|
||||
outdoorNodes = null;
|
||||
obstructedNodes.Clear();
|
||||
|
||||
GameMain.GameSession?.Campaign?.UpgradeManager?.OnUpgradesChanged?.TryDeregister(upgradeEventIdentifier);
|
||||
|
||||
@@ -1951,11 +1986,17 @@ namespace Barotrauma
|
||||
|
||||
visibleEntities = null;
|
||||
|
||||
bodyDist.Clear();
|
||||
bodies.Clear();
|
||||
|
||||
if (MainSub == this) { MainSub = null; }
|
||||
if (MainSubs[1] == this) { MainSubs[1] = null; }
|
||||
|
||||
ConnectedDockingPorts?.Clear();
|
||||
|
||||
Powered.ChangedConnections.Clear();
|
||||
Powered.Grids.Clear();
|
||||
|
||||
loaded.Remove(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,12 @@ namespace Barotrauma
|
||||
|
||||
bool hasCollider = wall.HasBody && !wall.IsPlatform && wall.StairDirection == Direction.None;
|
||||
Rectangle rect = wall.Rect;
|
||||
SetExtents(new Vector2(rect.X, rect.Y - rect.Height), new Vector2(rect.Right, rect.Y), hasCollider);
|
||||
|
||||
var transformedQuad = wall.GetTransformedQuad();
|
||||
AddPointToExtents(transformedQuad.A, hasCollider: hasCollider);
|
||||
AddPointToExtents(transformedQuad.B, hasCollider: hasCollider);
|
||||
AddPointToExtents(transformedQuad.C, hasCollider: hasCollider);
|
||||
AddPointToExtents(transformedQuad.D, hasCollider: hasCollider);
|
||||
if (hasCollider)
|
||||
{
|
||||
farseerBody.CreateRectangle(
|
||||
@@ -188,7 +193,8 @@ namespace Barotrauma
|
||||
if (hull.Submarine != submarine || hull.IdFreed) { continue; }
|
||||
|
||||
Rectangle rect = hull.Rect;
|
||||
SetExtents(new Vector2(rect.X, rect.Y - rect.Height), new Vector2(rect.Right, rect.Y), hasCollider: true);
|
||||
AddPointToExtents(new Vector2(rect.X, rect.Y - rect.Height), hasCollider: true);
|
||||
AddPointToExtents(new Vector2(rect.Right, rect.Y), hasCollider: true);
|
||||
|
||||
farseerBody.CreateRectangle(
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
@@ -221,33 +227,42 @@ namespace Barotrauma
|
||||
float simWidth = ConvertUnits.ToSimUnits(width);
|
||||
float simHeight = ConvertUnits.ToSimUnits(height);
|
||||
|
||||
if (radius > 0f || (width > 0f && height > 0f))
|
||||
{
|
||||
var transformedQuad = item.GetTransformedQuad();
|
||||
AddPointToExtents(transformedQuad.A, hasCollider: true);
|
||||
AddPointToExtents(transformedQuad.B, hasCollider: true);
|
||||
AddPointToExtents(transformedQuad.C, hasCollider: true);
|
||||
AddPointToExtents(transformedQuad.D, hasCollider: true);
|
||||
}
|
||||
|
||||
if (width > 0.0f && height > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
SetExtents(item.Position - new Vector2(width, height) / 2, item.Position + new Vector2(width, height) / 2, hasCollider: true);
|
||||
AddPointToExtents(item.Position - new Vector2(width, height) / 2, hasCollider: true);
|
||||
AddPointToExtents(item.Position + new Vector2(width, height) / 2, hasCollider: true);
|
||||
}
|
||||
else if (radius > 0.0f && width > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith));
|
||||
SetExtents(item.Position - new Vector2(width / 2 + radius, height / 2), item.Position + new Vector2(width / 2 + radius, height / 2), hasCollider: true);
|
||||
AddPointToExtents(item.Position - new Vector2(width / 2 + radius, height / 2), hasCollider: true);
|
||||
AddPointToExtents(item.Position + new Vector2(width / 2 + radius, height / 2), hasCollider: true);
|
||||
}
|
||||
else if (radius > 0.0f && height > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith));
|
||||
SetExtents(item.Position - new Vector2(width / 2, height / 2 + radius), item.Position + new Vector2(width / 2, height / 2 + radius), hasCollider: true);
|
||||
AddPointToExtents(item.Position - new Vector2(width / 2, height / 2 + radius), hasCollider: true);
|
||||
AddPointToExtents(item.Position + new Vector2(width / 2, height / 2 + radius), hasCollider: true);
|
||||
}
|
||||
else if (radius > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X);
|
||||
visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y);
|
||||
visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X);
|
||||
visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y);
|
||||
SetExtents(item.Position - new Vector2(radius, radius), item.Position + new Vector2(radius, radius), hasCollider: true);
|
||||
AddPointToExtents(item.Position - new Vector2(radius, radius), hasCollider: true);
|
||||
AddPointToExtents(item.Position + new Vector2(radius, radius), hasCollider: true);
|
||||
}
|
||||
item.StaticFixtures.ForEach(f => f.UserData = item);
|
||||
}
|
||||
@@ -268,18 +283,18 @@ namespace Barotrauma
|
||||
|
||||
Body = new PhysicsBody(farseerBody);
|
||||
|
||||
void SetExtents(Vector2 min, Vector2 max, bool hasCollider)
|
||||
void AddPointToExtents(Vector2 point, bool hasCollider)
|
||||
{
|
||||
visibleMinExtents.X = Math.Min(min.X, visibleMinExtents.X);
|
||||
visibleMinExtents.Y = Math.Min(min.Y, visibleMinExtents.Y);
|
||||
visibleMaxExtents.X = Math.Max(max.X, visibleMaxExtents.X);
|
||||
visibleMaxExtents.Y = Math.Max(max.Y, visibleMaxExtents.Y);
|
||||
visibleMinExtents.X = Math.Min(point.X, visibleMinExtents.X);
|
||||
visibleMinExtents.Y = Math.Min(point.Y, visibleMinExtents.Y);
|
||||
visibleMaxExtents.X = Math.Max(point.X, visibleMaxExtents.X);
|
||||
visibleMaxExtents.Y = Math.Max(point.Y, visibleMaxExtents.Y);
|
||||
if (hasCollider)
|
||||
{
|
||||
minExtents.X = Math.Min(min.X, minExtents.X);
|
||||
minExtents.Y = Math.Min(min.Y, minExtents.Y);
|
||||
maxExtents.X = Math.Max(max.X, maxExtents.X);
|
||||
maxExtents.Y = Math.Max(max.Y, maxExtents.Y);
|
||||
minExtents.X = Math.Min(point.X, minExtents.X);
|
||||
minExtents.Y = Math.Min(point.Y, minExtents.Y);
|
||||
maxExtents.X = Math.Max(point.X, maxExtents.X);
|
||||
maxExtents.Y = Math.Max(point.Y, maxExtents.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -800,7 +815,10 @@ namespace Barotrauma
|
||||
float damageAmount = contactDot * Body.Mass / limb.character.Mass;
|
||||
limb.character.LastDamageSource = submarine;
|
||||
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb,
|
||||
AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f);
|
||||
AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(),
|
||||
stun: 0.0f,
|
||||
playSound: true,
|
||||
attackImpulse: Vector2.Zero);
|
||||
|
||||
if (limb.character.IsDead)
|
||||
{
|
||||
|
||||
@@ -122,6 +122,9 @@ namespace Barotrauma
|
||||
|
||||
public OutpostModuleInfo OutpostModuleInfo { get; set; }
|
||||
public BeaconStationInfo BeaconStationInfo { get; set; }
|
||||
public WreckInfo WreckInfo { get; set; }
|
||||
|
||||
public ExtraSubmarineInfo GetExtraSubmarineInfo => BeaconStationInfo ?? WreckInfo as ExtraSubmarineInfo;
|
||||
|
||||
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
|
||||
|
||||
@@ -320,10 +323,14 @@ namespace Barotrauma
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
}
|
||||
if (original.BeaconStationInfo != null)
|
||||
else if (original.BeaconStationInfo != null)
|
||||
{
|
||||
BeaconStationInfo = new BeaconStationInfo(original.BeaconStationInfo);
|
||||
}
|
||||
else if (original.WreckInfo != null)
|
||||
{
|
||||
WreckInfo = new WreckInfo(original.WreckInfo);
|
||||
}
|
||||
#if CLIENT
|
||||
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
|
||||
#endif
|
||||
@@ -410,6 +417,10 @@ namespace Barotrauma
|
||||
{
|
||||
BeaconStationInfo = new BeaconStationInfo(this, SubmarineElement);
|
||||
}
|
||||
else if (Type == SubmarineType.Wreck)
|
||||
{
|
||||
WreckInfo = new WreckInfo(this, SubmarineElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +600,11 @@ namespace Barotrauma
|
||||
BeaconStationInfo.Save(newElement);
|
||||
BeaconStationInfo = new BeaconStationInfo(this, newElement);
|
||||
}
|
||||
else if (Type == SubmarineType.Wreck)
|
||||
{
|
||||
WreckInfo.Save(newElement);
|
||||
WreckInfo = new WreckInfo(this, newElement);
|
||||
}
|
||||
XDocument doc = new XDocument(newElement);
|
||||
|
||||
doc.Root.Add(new XAttribute("name", Name));
|
||||
|
||||
Reference in New Issue
Block a user