Release v0.15.12.0
This commit is contained in:
@@ -30,55 +30,44 @@ namespace Barotrauma
|
||||
|
||||
private bool idFreed;
|
||||
|
||||
public virtual bool Removed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public virtual bool Removed { get; private set; }
|
||||
|
||||
public bool IdFreed
|
||||
{
|
||||
get { return idFreed; }
|
||||
}
|
||||
public bool IdFreed => idFreed;
|
||||
|
||||
public readonly ushort ID;
|
||||
|
||||
public virtual Vector2 SimPosition
|
||||
public virtual Vector2 SimPosition => Vector2.Zero;
|
||||
|
||||
public virtual Vector2 Position => Vector2.Zero;
|
||||
|
||||
public virtual Vector2 WorldPosition => Submarine == null ? Position : Submarine.Position + Position;
|
||||
|
||||
public virtual Vector2 DrawPosition => Submarine == null ? Position : Submarine.DrawPosition + Position;
|
||||
|
||||
public Submarine Submarine { get; set; }
|
||||
|
||||
public AITarget AiTarget => aiTarget;
|
||||
|
||||
public bool InDetectable
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public virtual Vector2 Position
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public virtual Vector2 WorldPosition
|
||||
{
|
||||
get { return Submarine == null ? Position : Submarine.Position + Position; }
|
||||
}
|
||||
|
||||
public virtual Vector2 DrawPosition
|
||||
{
|
||||
get { return Submarine == null ? Position : Submarine.DrawPosition + Position; }
|
||||
}
|
||||
|
||||
public Submarine Submarine
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public AITarget AiTarget
|
||||
{
|
||||
get { return aiTarget; }
|
||||
}
|
||||
|
||||
public double SpawnTime
|
||||
{
|
||||
get { return spawnTime; }
|
||||
get
|
||||
{
|
||||
if (aiTarget != null)
|
||||
{
|
||||
return aiTarget.InDetectable;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.InDetectable = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double SpawnTime => spawnTime;
|
||||
private readonly double spawnTime;
|
||||
|
||||
public Entity(Submarine submarine, ushort id)
|
||||
@@ -88,7 +77,7 @@ namespace Barotrauma
|
||||
|
||||
if (id != NullEntityID && dictionary.ContainsKey(id))
|
||||
{
|
||||
throw new Exception($"ID {id} is taken by {dictionary[id].ToString()}");
|
||||
throw new Exception($"ID {id} is taken by {dictionary[id]}");
|
||||
}
|
||||
|
||||
//give a unique ID
|
||||
|
||||
@@ -13,10 +13,8 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Explosion
|
||||
{
|
||||
private static readonly List<Triplet<Explosion, Vector2, float>> prevExplosions = new List<Triplet<Explosion, Vector2, float>>();
|
||||
|
||||
public readonly Attack Attack;
|
||||
|
||||
|
||||
private readonly float force;
|
||||
|
||||
private readonly float cameraShake, cameraShakeRange;
|
||||
@@ -35,6 +33,11 @@ namespace Barotrauma
|
||||
private readonly float? flashRange;
|
||||
private readonly string decal;
|
||||
private readonly float decalSize;
|
||||
// used to apply friendly afflictions in an area without effects displaying
|
||||
private readonly bool abilityExplosion;
|
||||
private readonly bool applyToSelf;
|
||||
|
||||
private readonly float itemRepairStrength;
|
||||
|
||||
public float EmpStrength { get; set; }
|
||||
|
||||
@@ -63,22 +66,26 @@ namespace Barotrauma
|
||||
|
||||
force = element.GetAttributeFloat("force", 0.0f);
|
||||
|
||||
sparks = element.GetAttributeBool("sparks", true);
|
||||
shockwave = element.GetAttributeBool("shockwave", true);
|
||||
flames = element.GetAttributeBool("flames", true);
|
||||
underwaterBubble = element.GetAttributeBool("underwaterbubble", true);
|
||||
smoke = element.GetAttributeBool("smoke", true);
|
||||
abilityExplosion = element.GetAttributeBool("abilityexplosion", false);
|
||||
applyToSelf = element.GetAttributeBool("applytoself", true);
|
||||
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", true);
|
||||
bool showEffects = !abilityExplosion;
|
||||
sparks = element.GetAttributeBool("sparks", showEffects);
|
||||
shockwave = element.GetAttributeBool("shockwave", showEffects);
|
||||
flames = element.GetAttributeBool("flames", showEffects);
|
||||
underwaterBubble = element.GetAttributeBool("underwaterbubble", showEffects);
|
||||
smoke = element.GetAttributeBool("smoke", showEffects);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames);
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", showEffects);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames && showEffects);
|
||||
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", new string[0], convertToLowerInvariant: true);
|
||||
|
||||
ignoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
onlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
onlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
|
||||
flash = element.GetAttributeBool("flash", true);
|
||||
flash = element.GetAttributeBool("flash", showEffects);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
|
||||
flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow);
|
||||
@@ -86,15 +93,18 @@ namespace Barotrauma
|
||||
EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
|
||||
BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f);
|
||||
|
||||
decal = element.GetAttributeString("decal", "");
|
||||
itemRepairStrength = element.GetAttributeFloat("itemrepairstrength", 0.0f);
|
||||
|
||||
decal = element.GetAttributeString("decal", "");
|
||||
decalSize = element.GetAttributeFloat(1.0f, "decalSize", "decalsize");
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", Attack.Range * 0.1f);
|
||||
cameraShakeRange = element.GetAttributeFloat("camerashakerange", Attack.Range);
|
||||
cameraShake = element.GetAttributeFloat("camerashake", showEffects ? Attack.Range * 0.1f : 0f);
|
||||
cameraShakeRange = element.GetAttributeFloat("camerashakerange", showEffects ? Attack.Range : 0f);
|
||||
|
||||
screenColorRange = element.GetAttributeFloat("screencolorrange", Attack.Range * 0.1f);
|
||||
screenColorRange = element.GetAttributeFloat("screencolorrange", showEffects ? Attack.Range * 0.1f : 0f);
|
||||
screenColor = element.GetAttributeColor("screencolor", Color.Transparent);
|
||||
screenColorDuration = element.GetAttributeFloat("screencolorduration", 0.1f);
|
||||
|
||||
}
|
||||
|
||||
public void DisableParticles()
|
||||
@@ -107,19 +117,8 @@ namespace Barotrauma
|
||||
underwaterBubble = false;
|
||||
}
|
||||
|
||||
public List<Triplet<Explosion, Vector2, float>> GetRecentExplosions(float maxSecondsAgo)
|
||||
{
|
||||
return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo);
|
||||
}
|
||||
|
||||
public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null)
|
||||
{
|
||||
prevExplosions.Add(new Triplet<Explosion, Vector2, float>(this, worldPosition, (float)Timing.TotalTime));
|
||||
if (prevExplosions.Count > 100)
|
||||
{
|
||||
prevExplosions.RemoveAt(0);
|
||||
}
|
||||
|
||||
Hull hull = Hull.FindHull(worldPosition);
|
||||
ExplodeProjSpecific(worldPosition, hull);
|
||||
|
||||
@@ -129,6 +128,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float displayRange = Attack.Range;
|
||||
if (damageSource is Item sourceItem)
|
||||
{
|
||||
displayRange *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius);
|
||||
Attack.DamageMultiplier *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage);
|
||||
}
|
||||
|
||||
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
@@ -143,7 +147,7 @@ namespace Barotrauma
|
||||
|
||||
if (displayRange < 0.1f) { return; }
|
||||
|
||||
if (Attack.GetStructureDamage(1.0f) > 0.0f || Attack.GetLevelWallDamage(1.0f) > 0.0f)
|
||||
if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f))
|
||||
{
|
||||
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker);
|
||||
}
|
||||
@@ -180,12 +184,29 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f))
|
||||
if (itemRepairStrength > 0.0f)
|
||||
{
|
||||
float displayRangeSqr = displayRange * displayRange;
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
|
||||
if (distSqr > displayRangeSqr) continue;
|
||||
|
||||
float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange;
|
||||
//repair repairable items
|
||||
if (item.Repairables.Any())
|
||||
{
|
||||
item.Condition += itemRepairStrength * distFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f) && !abilityExplosion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, applyToSelf);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -195,9 +216,9 @@ namespace Barotrauma
|
||||
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
|
||||
float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
|
||||
if (dist > Attack.Range) { continue; }
|
||||
if (dist > displayRange) { continue; }
|
||||
|
||||
if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
|
||||
if (dist < displayRange * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
|
||||
{
|
||||
//don't apply OnFire effects if the item is inside a fireproof container
|
||||
//(or if it's inside a container that's inside a fireproof container, etc)
|
||||
@@ -224,7 +245,7 @@ namespace Barotrauma
|
||||
|
||||
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
|
||||
{
|
||||
float distFactor = 1.0f - dist / Attack.Range;
|
||||
float distFactor = 1.0f - dist / displayRange;
|
||||
float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
@@ -239,7 +260,7 @@ namespace Barotrauma
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, bool applyToSelf)
|
||||
{
|
||||
if (attack.Range <= 0.0f) { return; }
|
||||
|
||||
@@ -254,6 +275,8 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (c == attacker && !applyToSelf) { continue; }
|
||||
|
||||
if (onlyInside && c.Submarine == null) { continue; }
|
||||
else if (onlyOutside && c.Submarine != null) { continue; }
|
||||
|
||||
@@ -317,7 +340,7 @@ namespace Barotrauma
|
||||
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
|
||||
Vector2 dir = worldPosition - limb.WorldPosition;
|
||||
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker);
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier);
|
||||
damages.Add(limb, attackResult.Damage);
|
||||
|
||||
if (attack.StatusEffects != null && attack.StatusEffects.Any())
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Barotrauma
|
||||
partial class BackgroundSection
|
||||
{
|
||||
public Rectangle Rect;
|
||||
public int Index;
|
||||
public int RowIndex;
|
||||
public ushort Index;
|
||||
public ushort RowIndex;
|
||||
|
||||
private Vector4 colorVector4;
|
||||
private Color color;
|
||||
@@ -39,7 +39,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public BackgroundSection(Rectangle rect, int index, int rowIndex)
|
||||
public BackgroundSection(Rectangle rect, ushort index, ushort rowIndex)
|
||||
{
|
||||
Rect = rect;
|
||||
Index = index;
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
Color = DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X);
|
||||
}
|
||||
|
||||
public BackgroundSection(Rectangle rect, int index, float colorStrength, Color color, int rowIndex)
|
||||
public BackgroundSection(Rectangle rect, ushort index, float colorStrength, Color color, ushort rowIndex)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
|
||||
|
||||
@@ -471,9 +471,8 @@ namespace Barotrauma
|
||||
{
|
||||
aiTarget = new AITarget(this)
|
||||
{
|
||||
MinSightRange = 2000,
|
||||
MinSightRange = 1000,
|
||||
MaxSightRange = 5000,
|
||||
MaxSoundRange = 5000,
|
||||
SoundRange = 0
|
||||
};
|
||||
}
|
||||
@@ -674,6 +673,9 @@ namespace Barotrauma
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
BackgroundSections?.Clear();
|
||||
submergedSections?.Clear();
|
||||
|
||||
List<FireSource> fireSourcesToRemove = new List<FireSource>(FireSources);
|
||||
foreach (FireSource fireSource in fireSourcesToRemove)
|
||||
{
|
||||
@@ -784,7 +786,7 @@ namespace Barotrauma
|
||||
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange;
|
||||
aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : MathHelper.Lerp(aiTarget.MinSightRange, aiTarget.MaxSightRange, Submarine.Velocity.Length() / 10);
|
||||
aiTarget.SoundRange -= deltaTime * 1000.0f;
|
||||
}
|
||||
|
||||
@@ -932,7 +934,7 @@ namespace Barotrauma
|
||||
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
|
||||
{
|
||||
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f);
|
||||
item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1241,6 +1243,44 @@ namespace Barotrauma
|
||||
return "RoomName.Sub" + roomPos.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this hull or any of the items inside it tagged as "airlock"?
|
||||
/// </summary>
|
||||
public bool IsTaggedAirlock()
|
||||
{
|
||||
if (RoomName != null && RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != this && item.HasTag("airlock"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does this hull have any doors leading outside?
|
||||
/// </summary>
|
||||
/// <param name="character">Used to check if this character has access to the door leading outside</param>
|
||||
public bool LeadsOutside(Character character)
|
||||
{
|
||||
foreach (var gap in ConnectedGaps)
|
||||
{
|
||||
if (gap.ConnectedDoor == null) { continue; }
|
||||
if (gap.IsRoomToRoom) { continue; }
|
||||
if (!gap.ConnectedDoor.CanBeTraversed && (character == null || !gap.ConnectedDoor.HasAccess(character))) { continue; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region BackgroundSections
|
||||
private void CreateBackgroundSections()
|
||||
{
|
||||
@@ -1260,9 +1300,9 @@ namespace Barotrauma
|
||||
{
|
||||
for (int x = 0; x < xBackgroundMax; x++)
|
||||
{
|
||||
int index = BackgroundSections.Count;
|
||||
ushort index = (ushort)BackgroundSections.Count;
|
||||
int sector = (int)Math.Floor(index / (float)sectorWidth - xSectors * y) + y / sectorHeight * (int)Math.Ceiling(xSectors);
|
||||
BackgroundSections.Add(new BackgroundSection(new Rectangle(x * sectionWidth, y * -sectionHeight, sectionWidth, sectionHeight), index, y));
|
||||
BackgroundSections.Add(new BackgroundSection(new Rectangle(x * sectionWidth, y * -sectionHeight, sectionWidth, sectionHeight), index, (ushort)y));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -528,6 +528,7 @@ namespace Barotrauma
|
||||
|
||||
//create a tunnel from the lowest point in the main path to the abyss
|
||||
//to ensure there's a way to the abyss in all levels
|
||||
Tunnel abyssTunnel = null;
|
||||
if (GenerationParams.CreateHoleToAbyss)
|
||||
{
|
||||
Point lowestPoint = mainPath.Nodes.First();
|
||||
@@ -535,7 +536,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (pathNode.Y < lowestPoint.Y) { lowestPoint = pathNode; }
|
||||
}
|
||||
var abyssTunnel = new Tunnel(
|
||||
abyssTunnel = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { lowestPoint, new Point(lowestPoint.X, 0) },
|
||||
minWidth / 2, parentTunnel: mainPath);
|
||||
@@ -546,7 +547,7 @@ namespace Barotrauma
|
||||
for (int j = 0; j < sideTunnelCount; j++)
|
||||
{
|
||||
if (mainPath.Nodes.Count < 4) { break; }
|
||||
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole);
|
||||
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole && t != abyssTunnel);
|
||||
Tunnel tunnelToBranchOff = validTunnels[Rand.Int(validTunnels.Count, Rand.RandSync.Server)];
|
||||
if (tunnelToBranchOff == null) { tunnelToBranchOff = mainPath; }
|
||||
|
||||
@@ -559,7 +560,7 @@ namespace Barotrauma
|
||||
Tunnels.Add(new Tunnel(TunnelType.SidePath, sidePathNodes, pathWidth, parentTunnel: tunnelToBranchOff));
|
||||
}
|
||||
|
||||
CalculateTunnelDistanceField(density: 1000);
|
||||
CalculateTunnelDistanceField(null);
|
||||
GenerateSeaFloorPositions();
|
||||
GenerateAbyssArea();
|
||||
GenerateCaves(mainPath);
|
||||
@@ -691,7 +692,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
GenerateWaypoints(tunnel, parentTunnel: tunnel.ParentTunnel);
|
||||
|
||||
bool connectToParentTunnel = tunnel.Type != TunnelType.Cave || tunnel.ParentTunnel.Type == TunnelType.Cave;
|
||||
GenerateWaypoints(tunnel, parentTunnel: connectToParentTunnel ? tunnel.ParentTunnel : null);
|
||||
|
||||
EnlargePath(tunnel.Cells, tunnel.MinWidth);
|
||||
foreach (var pathCell in tunnel.Cells)
|
||||
{
|
||||
@@ -791,6 +795,15 @@ namespace Barotrauma
|
||||
cells.AddRange(abyssIsland.Cells);
|
||||
}
|
||||
|
||||
List<Point> ruinPositions = new List<Point>();
|
||||
for (int i = 0; i < GenerationParams.RuinCount; i++)
|
||||
{
|
||||
Point ruinSize = new Point(5000);
|
||||
ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
|
||||
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize)));
|
||||
CalculateTunnelDistanceField(ruinPositions);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// initialize the cells that are still left and insert them into the cell grid
|
||||
//----------------------------------------------------------------------------------
|
||||
@@ -813,7 +826,9 @@ namespace Barotrauma
|
||||
//----------------------------------------------------------------------------------
|
||||
// mirror if needed
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
int asdfasdf = Rand.Int(int.MaxValue, Rand.RandSync.Server);
|
||||
|
||||
if (mirror)
|
||||
{
|
||||
HashSet<GraphEdge> mirroredEdges = new HashSet<GraphEdge>();
|
||||
@@ -849,6 +864,21 @@ namespace Barotrauma
|
||||
foreach (AbyssIsland island in AbyssIslands)
|
||||
{
|
||||
island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height);
|
||||
foreach (var cell in island.Cells)
|
||||
{
|
||||
if (!mirroredSites.Contains(cell.Site))
|
||||
{
|
||||
if (cell.Site.Coord.X % GridCellSize < 1.0f &&
|
||||
cell.Site.Coord.X % GridCellSize >= 0.0f) { cell.Site.Coord.X += 1.0f; }
|
||||
cell.Site.Coord.X = borders.Width - cell.Site.Coord.X;
|
||||
mirroredSites.Add(cell.Site);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ruinPositions.Count; i++)
|
||||
{
|
||||
ruinPositions[i] = new Point(borders.Width - ruinPositions[i].X, ruinPositions[i].Y);
|
||||
}
|
||||
|
||||
foreach (Cave cave in Caves)
|
||||
@@ -896,7 +926,7 @@ namespace Barotrauma
|
||||
startExitPosition.X = borders.Width - startExitPosition.X;
|
||||
endExitPosition.X = borders.Width - endExitPosition.X;
|
||||
|
||||
CalculateTunnelDistanceField(density: 1000);
|
||||
CalculateTunnelDistanceField(ruinPositions);
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
@@ -913,8 +943,23 @@ namespace Barotrauma
|
||||
foreach (Cave cave in Caves)
|
||||
{
|
||||
if (cave.Area.Y > 0)
|
||||
{
|
||||
CreatePathToClosestTunnel(cave.StartPos);
|
||||
{
|
||||
List<VoronoiCell> cavePathCells = CreatePathToClosestTunnel(cave.StartPos);
|
||||
|
||||
var mainTunnel = cave.Tunnels.Find(t => t.ParentTunnel.Type != TunnelType.Cave);
|
||||
|
||||
WayPoint prevWp = mainTunnel.WayPoints.First();
|
||||
if (prevWp != null)
|
||||
{
|
||||
for (int i = 0; i < cavePathCells.Count; i++)
|
||||
{
|
||||
var newWaypoint = new WayPoint(cavePathCells[i].Center, SpawnType.Path, submarine: null);
|
||||
ConnectWaypoints(prevWp, newWaypoint, 500.0f);
|
||||
prevWp = newWaypoint;
|
||||
}
|
||||
var closestPathPoint = FindClosestWayPoint(prevWp.WorldPosition, mainTunnel.ParentTunnel.WayPoints);
|
||||
ConnectWaypoints(prevWp, closestPathPoint, 500.0f);
|
||||
}
|
||||
}
|
||||
|
||||
List<VoronoiCell> caveCells = new List<VoronoiCell>();
|
||||
@@ -940,9 +985,10 @@ namespace Barotrauma
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
Ruins = new List<Ruin>();
|
||||
for (int i = 0; i < GenerationParams.RuinCount; i++)
|
||||
for (int i = 0; i < ruinPositions.Count; i++)
|
||||
{
|
||||
GenerateRuin(mainPath, mirror);
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed) + i);
|
||||
GenerateRuin(ruinPositions[i], mirror);
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
@@ -1004,7 +1050,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if CLIENT
|
||||
List<(List<VoronoiCell> cells, Cave parentCave)> cellBatches = new List<(List<VoronoiCell>, Cave)>
|
||||
{
|
||||
@@ -1083,7 +1128,6 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
@@ -1101,6 +1145,11 @@ namespace Barotrauma
|
||||
// connect side paths and cave branches to their parents
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
foreach (Ruin ruin in Ruins)
|
||||
{
|
||||
GenerateRuinWayPoints(ruin);
|
||||
}
|
||||
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
{
|
||||
if (tunnel.ParentTunnel == null) { continue; }
|
||||
@@ -1343,18 +1392,7 @@ namespace Barotrauma
|
||||
|
||||
if (wayPoints.Count > 1)
|
||||
{
|
||||
wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
||||
}
|
||||
|
||||
for (int n = 0; n < wayPoints.Count; n++)
|
||||
{
|
||||
if (wayPoints[n].Position != newWaypoint.Position) { continue; }
|
||||
|
||||
wayPoints[n].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[n]);
|
||||
|
||||
break;
|
||||
wayPoints[wayPoints.Count - 2].ConnectTo(newWaypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1363,19 +1401,17 @@ namespace Barotrauma
|
||||
//connect to the tunnel we're branching off from
|
||||
if (parentTunnel != null)
|
||||
{
|
||||
var parentStart = FindClosestWayPoint(wayPoints.First(), parentTunnel);
|
||||
var parentStart = FindClosestWayPoint(wayPoints.First().WorldPosition, parentTunnel);
|
||||
if (parentStart != null)
|
||||
{
|
||||
wayPoints.First().linkedTo.Add(parentStart);
|
||||
parentStart.linkedTo.Add(wayPoints.First());
|
||||
wayPoints.First().ConnectTo(parentStart);
|
||||
}
|
||||
if (tunnel.Type != TunnelType.Cave || tunnel.ParentTunnel.Type == TunnelType.Cave)
|
||||
{
|
||||
var parentEnd = FindClosestWayPoint(wayPoints.Last(), parentTunnel);
|
||||
var parentEnd = FindClosestWayPoint(wayPoints.Last().WorldPosition, parentTunnel);
|
||||
if (parentEnd != null)
|
||||
{
|
||||
wayPoints.Last().linkedTo.Add(parentEnd);
|
||||
parentEnd.linkedTo.Add(wayPoints.Last());
|
||||
wayPoints.Last().ConnectTo(parentEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1385,45 +1421,58 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (WayPoint wayPoint in tunnel.WayPoints)
|
||||
{
|
||||
var closestWaypoint = FindClosestWayPoint(wayPoint, parentTunnel);
|
||||
var closestWaypoint = FindClosestWayPoint(wayPoint.WorldPosition, parentTunnel);
|
||||
if (closestWaypoint == null) { continue; }
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(wayPoint.WorldPosition),
|
||||
ConvertUnits.ToSimUnits(closestWaypoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null)
|
||||
{
|
||||
Vector2 diff = closestWaypoint.WorldPosition - wayPoint.WorldPosition;
|
||||
float dist = diff.Length();
|
||||
float step = ConvertUnits.ToDisplayUnits(Steering.AutopilotMinDistToPathNode) * 0.8f;
|
||||
|
||||
WayPoint prevWaypoint = wayPoint;
|
||||
for (float x = step; x < dist - step; x += step)
|
||||
{
|
||||
var newWaypoint = new WayPoint(wayPoint.WorldPosition + (diff / dist * x), SpawnType.Path, submarine: null)
|
||||
{
|
||||
Tunnel = tunnel
|
||||
};
|
||||
prevWaypoint.linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(prevWaypoint);
|
||||
prevWaypoint = newWaypoint;
|
||||
}
|
||||
prevWaypoint.linkedTo.Add(closestWaypoint);
|
||||
closestWaypoint.linkedTo.Add(prevWaypoint);
|
||||
ConnectWaypoints(wayPoint, closestWaypoint, step).ForEach(wp => wp.Tunnel = tunnel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static WayPoint FindClosestWayPoint(WayPoint wayPoint, Tunnel otherTunnel)
|
||||
private List<WayPoint> ConnectWaypoints(WayPoint wp1, WayPoint wp2, float interval)
|
||||
{
|
||||
List<WayPoint> newWaypoints = new List<WayPoint>();
|
||||
|
||||
Vector2 diff = wp2.WorldPosition - wp1.WorldPosition;
|
||||
float dist = diff.Length();
|
||||
|
||||
WayPoint prevWaypoint = wp1;
|
||||
for (float x = interval; x < dist - interval; x += interval)
|
||||
{
|
||||
var newWaypoint = new WayPoint(wp1.WorldPosition + (diff / dist * x), SpawnType.Path, submarine: null);
|
||||
prevWaypoint.ConnectTo(newWaypoint);
|
||||
prevWaypoint = newWaypoint;
|
||||
newWaypoints.Add(newWaypoint);
|
||||
}
|
||||
prevWaypoint.ConnectTo(wp2);
|
||||
|
||||
return newWaypoints;
|
||||
}
|
||||
|
||||
private static WayPoint FindClosestWayPoint(Vector2 worldPosition, Tunnel otherTunnel)
|
||||
{
|
||||
return FindClosestWayPoint(worldPosition, otherTunnel.WayPoints);
|
||||
}
|
||||
|
||||
private static WayPoint FindClosestWayPoint(Vector2 worldPosition, IEnumerable<WayPoint> waypoints, Func<WayPoint, bool> filter = null)
|
||||
{
|
||||
float closestDist = float.PositiveInfinity;
|
||||
WayPoint closestWayPoint = null;
|
||||
foreach (WayPoint otherWayPoint in otherTunnel.WayPoints)
|
||||
foreach (WayPoint otherWayPoint in waypoints)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(otherWayPoint.WorldPosition, wayPoint.WorldPosition);
|
||||
float dist = Vector2.DistanceSquared(otherWayPoint.WorldPosition, worldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
if (filter != null)
|
||||
{
|
||||
if (!filter(otherWayPoint)) { continue; }
|
||||
}
|
||||
closestDist = dist;
|
||||
closestWayPoint = otherWayPoint;
|
||||
|
||||
}
|
||||
}
|
||||
return closestWayPoint;
|
||||
@@ -1706,7 +1755,7 @@ namespace Barotrauma
|
||||
|
||||
GenerateCave(caveParams, parentTunnel, cavePos, caveSize);
|
||||
|
||||
CalculateTunnelDistanceField(density: 1000);
|
||||
CalculateTunnelDistanceField(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1788,84 +1837,172 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateRuin(Tunnel mainPath, bool mirror)
|
||||
private void GenerateRuin(Point ruinPos, bool mirror)
|
||||
{
|
||||
var ruinGenerationParams = RuinGenerationParams.GetRandom();
|
||||
var ruinGenerationParams = RuinGenerationParams.GetRandom(Rand.RandSync.Server);
|
||||
|
||||
Point ruinSize = new Point(
|
||||
Rand.Range(ruinGenerationParams.SizeMin.X, ruinGenerationParams.SizeMax.X, Rand.RandSync.Server),
|
||||
Rand.Range(ruinGenerationParams.SizeMin.Y, ruinGenerationParams.SizeMax.Y, Rand.RandSync.Server));
|
||||
int ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) / 2;
|
||||
|
||||
Point ruinPos = FindPosAwayFromMainPath((ruinRadius + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
|
||||
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize));
|
||||
|
||||
VoronoiCell closestPathCell = null;
|
||||
double closestDist = 0.0f;
|
||||
foreach (VoronoiCell pathCell in mainPath.Cells)
|
||||
LocationType locationType = StartLocation?.Type;
|
||||
if (locationType == null)
|
||||
{
|
||||
double dist = MathUtils.DistanceSquared(pathCell.Site.Coord.X, pathCell.Site.Coord.Y, ruinPos.X, ruinPos.Y);
|
||||
if (closestPathCell == null || dist < closestDist)
|
||||
locationType = LocationType.List.GetRandom(Rand.RandSync.Server);
|
||||
if (ruinGenerationParams.AllowedLocationTypes.Any())
|
||||
{
|
||||
closestPathCell = pathCell;
|
||||
closestDist = dist;
|
||||
locationType = LocationType.List.Where(lt =>
|
||||
ruinGenerationParams.AllowedLocationTypes.Any(allowedType =>
|
||||
allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(Rand.RandSync.Server);
|
||||
}
|
||||
}
|
||||
|
||||
var ruin = new Ruin(closestPathCell, cells, ruinGenerationParams, new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize), mirror);
|
||||
|
||||
var ruin = new Ruin(this, ruinGenerationParams, locationType, ruinPos, mirror);
|
||||
Ruins.Add(ruin);
|
||||
|
||||
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
|
||||
// TODO: autogenerate waypoints inside the ruins and connect them to the main path in multiple places.
|
||||
// We need the waypoints for the AI navigation and we could use them for spawning the creatures too.
|
||||
int waypointCount = 0;
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
var tooClose = GetTooCloseCells(ruinPos.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 4);
|
||||
|
||||
foreach (VoronoiCell cell in tooClose)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Enemy || wp.Submarine != null) { continue; }
|
||||
if (ruin.RuinShapes.Any(rs => rs.Rect.Contains(wp.WorldPosition)))
|
||||
if (cell.CellType == CellType.Empty) { continue; }
|
||||
if (ExtraWalls.Any(w => w.Cells.Contains(cell))) { continue; }
|
||||
foreach (GraphEdge e in cell.Edges)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin));
|
||||
waypointCount++;
|
||||
}
|
||||
}
|
||||
|
||||
//not enough waypoints inside ruins -> create some spawn positions manually
|
||||
for (int i = 0; i < 4 - waypointCount && i < ruin.RuinShapes.Count; i++)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin));
|
||||
}
|
||||
|
||||
foreach (RuinShape ruinShape in ruin.RuinShapes)
|
||||
{
|
||||
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height) * 4);
|
||||
|
||||
foreach (VoronoiCell cell in tooClose)
|
||||
{
|
||||
if (cell.CellType == CellType.Empty) { continue; }
|
||||
if (ExtraWalls.Any(w => w.Cells.Contains(cell))) { continue; }
|
||||
foreach (GraphEdge e in cell.Edges)
|
||||
if (ruin.Area.Contains(e.Point1) || ruin.Area.Contains(e.Point2) ||
|
||||
MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, ruin.Area, out _))
|
||||
{
|
||||
Rectangle rect = ruinShape.Rect;
|
||||
rect.Y += rect.Height;
|
||||
if (ruinShape.Rect.Contains(e.Point1) || ruinShape.Rect.Contains(e.Point2) ||
|
||||
MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, rect, out _))
|
||||
cell.CellType = CellType.Removed;
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
{
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
{
|
||||
cellGrid[x, y].Remove(cell);
|
||||
}
|
||||
cellGrid[x, y].Remove(cell);
|
||||
}
|
||||
cells.Remove(cell);
|
||||
break;
|
||||
}
|
||||
cells.Remove(cell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreatePathToClosestTunnel(ruinPos);
|
||||
ruin.PathCells = CreatePathToClosestTunnel(ruin.Area.Center);
|
||||
}
|
||||
|
||||
private void GenerateRuinWayPoints(Ruin ruin)
|
||||
{
|
||||
var tooClose = GetTooCloseCells(ruin.Area.Center.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 6);
|
||||
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
float outSideWaypointInterval = 500.0f;
|
||||
WayPoint[,] cornerWaypoint = new WayPoint[2, 2];
|
||||
Rectangle waypointArea = ruin.Area;
|
||||
waypointArea.Inflate(100, 100);
|
||||
|
||||
//generate waypoints around the ruin
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
for (float x = waypointArea.X + outSideWaypointInterval; x < waypointArea.Right - outSideWaypointInterval; x += outSideWaypointInterval)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, waypointArea.Y + waypointArea.Height * i), SpawnType.Path, null)
|
||||
{
|
||||
Ruin = ruin
|
||||
};
|
||||
wayPoints.Add(wayPoint);
|
||||
if (x == waypointArea.X + outSideWaypointInterval)
|
||||
{
|
||||
cornerWaypoint[i, 0] = wayPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
wayPoint.ConnectTo(wayPoints[wayPoints.Count - 2]);
|
||||
}
|
||||
}
|
||||
cornerWaypoint[i, 1] = wayPoints[wayPoints.Count - 1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
WayPoint wayPoint = null;
|
||||
for (float y = waypointArea.Y; y < waypointArea.Y + waypointArea.Height; y += outSideWaypointInterval)
|
||||
{
|
||||
wayPoint = new WayPoint(new Vector2(waypointArea.X + waypointArea.Width * i, y), SpawnType.Path, null)
|
||||
{
|
||||
Ruin = ruin
|
||||
};
|
||||
wayPoints.Add(wayPoint);
|
||||
if (y == waypointArea.Y)
|
||||
{
|
||||
wayPoint.ConnectTo(cornerWaypoint[0, i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
wayPoint.ConnectTo(wayPoints[wayPoints.Count - 2]);
|
||||
}
|
||||
}
|
||||
wayPoint.ConnectTo(cornerWaypoint[1, i]);
|
||||
}
|
||||
|
||||
//remove waypoints that are inside walls
|
||||
for (int i = wayPoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
WayPoint wp = wayPoints[i];
|
||||
var overlappingCell = tooClose.Find(c => c.CellType != CellType.Removed && c.IsPointInside(wp.WorldPosition));
|
||||
if (overlappingCell == null) { continue; }
|
||||
if (wp.linkedTo.Count > 1)
|
||||
{
|
||||
WayPoint linked1 = wp.linkedTo[0] as WayPoint;
|
||||
WayPoint linked2 = wp.linkedTo[1] as WayPoint;
|
||||
linked1.ConnectTo(linked2);
|
||||
}
|
||||
wp.Remove();
|
||||
wayPoints.RemoveAt(i);
|
||||
}
|
||||
|
||||
//connect ruin entrances to the outside waypoints
|
||||
foreach (Gap g in Gap.GapList)
|
||||
{
|
||||
if (g.Submarine != ruin.Submarine || g.IsRoomToRoom || g.linkedTo.Count == 0) { continue; }
|
||||
var gapWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == g);
|
||||
if (gapWaypoint == null) { continue; }
|
||||
|
||||
//place another waypoint in front of the entrance
|
||||
Vector2 entranceDir = Vector2.Zero;
|
||||
if (g.IsHorizontal)
|
||||
{
|
||||
entranceDir = Vector2.UnitX * 2 * Math.Sign(g.WorldPosition.X - g.linkedTo[0].WorldPosition.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
entranceDir = Vector2.UnitY * 2 * Math.Sign(g.WorldPosition.Y - g.linkedTo[0].WorldPosition.Y);
|
||||
}
|
||||
var entranceWayPoint = new WayPoint(g.WorldPosition + entranceDir * 64.0f, SpawnType.Path, null)
|
||||
{
|
||||
Ruin = ruin
|
||||
};
|
||||
entranceWayPoint.ConnectTo(gapWaypoint);
|
||||
var closestWp = FindClosestWayPoint(entranceWayPoint.WorldPosition, wayPoints, (wp) =>
|
||||
{
|
||||
return Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(wp.WorldPosition),
|
||||
ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null;
|
||||
});
|
||||
if (closestWp == null) { continue; }
|
||||
ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval);
|
||||
}
|
||||
|
||||
//create a waypoint path from the ruin to the closest tunnel
|
||||
WayPoint prevWp = FindClosestWayPoint(ruin.PathCells.First().Center, wayPoints, (wp) =>
|
||||
{
|
||||
return Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(wp.WorldPosition),
|
||||
ConvertUnits.ToSimUnits(ruin.PathCells.First().Center), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null;
|
||||
});
|
||||
if (prevWp != null)
|
||||
{
|
||||
for (int i = 0; i < ruin.PathCells.Count; i++)
|
||||
{
|
||||
var newWaypoint = new WayPoint(ruin.PathCells[i].Center, SpawnType.Path, submarine: null);
|
||||
ConnectWaypoints(prevWp, newWaypoint, outSideWaypointInterval);
|
||||
prevWp = newWaypoint;
|
||||
}
|
||||
var closestPathPoint = FindClosestWayPoint(prevWp.WorldPosition, Tunnels.SelectMany(t => t.WayPoints));
|
||||
ConnectWaypoints(prevWp, closestPathPoint, outSideWaypointInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null)
|
||||
@@ -1891,8 +2028,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateTunnelDistanceField(int density)
|
||||
private void CalculateTunnelDistanceField(List<Point> ruinPositions)
|
||||
{
|
||||
int density = 1000;
|
||||
distanceField = new List<(Point point, double distance)>();
|
||||
|
||||
if (Mirrored)
|
||||
@@ -1927,6 +2065,23 @@ namespace Barotrauma
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
|
||||
}
|
||||
}
|
||||
if (ruinPositions != null)
|
||||
{
|
||||
int ruinSize = 10000;
|
||||
foreach (Point ruinPos in ruinPositions)
|
||||
{
|
||||
double xDiff = Math.Abs(point.X - ruinPos.X);
|
||||
double yDiff = Math.Abs(point.Y - ruinPos.Y);
|
||||
if (xDiff < ruinSize || yDiff < ruinSize)
|
||||
{
|
||||
shortestDistSqr = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
shortestDistSqr = Math.Min(xDiff * xDiff + yDiff * yDiff, shortestDistSqr);
|
||||
}
|
||||
}
|
||||
}
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
|
||||
@@ -2564,10 +2719,18 @@ namespace Barotrauma
|
||||
if (PositionsOfInterest.Any(p => p.PositionType == PositionType.Cave))
|
||||
{
|
||||
positionType = PositionType.Cave;
|
||||
if (allValidLocations.Any(l => l.Edge.NextToCave))
|
||||
{
|
||||
allValidLocations.RemoveAll(l => !l.Edge.NextToCave);
|
||||
}
|
||||
}
|
||||
else if (PositionsOfInterest.Any(p => p.PositionType == PositionType.SidePath))
|
||||
{
|
||||
positionType = PositionType.SidePath;
|
||||
if (allValidLocations.Any(l => l.Edge.NextToSidePath))
|
||||
{
|
||||
allValidLocations.RemoveAll(l => !l.Edge.NextToSidePath);
|
||||
}
|
||||
}
|
||||
|
||||
var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.Server);
|
||||
@@ -2946,7 +3109,7 @@ namespace Barotrauma
|
||||
return closestCell;
|
||||
}
|
||||
|
||||
private void CreatePathToClosestTunnel(Point pos)
|
||||
private List<VoronoiCell> CreatePathToClosestTunnel(Point pos)
|
||||
{
|
||||
VoronoiCell closestPathCell = null;
|
||||
double closestDist = 0.0f;
|
||||
@@ -2966,6 +3129,7 @@ namespace Barotrauma
|
||||
|
||||
//cast a ray from the closest path cell towards the position and remove the cells it hits
|
||||
List<VoronoiCell> validCells = cells.FindAll(c => c.CellType != CellType.Empty && c.CellType != CellType.Removed);
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>() { closestPathCell };
|
||||
foreach (VoronoiCell cell in validCells)
|
||||
{
|
||||
foreach (GraphEdge e in cell.Edges)
|
||||
@@ -2980,6 +3144,7 @@ namespace Barotrauma
|
||||
cellGrid[x, y].Remove(cell);
|
||||
}
|
||||
}
|
||||
pathCells.Add(cell);
|
||||
cells.Remove(cell);
|
||||
|
||||
//go through the edges of this cell and find the ones that are next to a removed cell
|
||||
@@ -3012,12 +3177,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pathCells.Sort((c1, c2) => { return Vector2.DistanceSquared(c1.Center, pos.ToVector2()).CompareTo(Vector2.DistanceSquared(c2.Center, pos.ToVector2())); });
|
||||
return pathCells;
|
||||
}
|
||||
|
||||
public string GetWreckIDTag(string originalTag, Submarine wreck)
|
||||
@@ -3049,10 +3214,8 @@ namespace Barotrauma
|
||||
var waypoints = WayPoint.WayPointList.Where(wp =>
|
||||
wp.Submarine == null &&
|
||||
wp.SpawnType == SpawnType.Path &&
|
||||
wp.WorldPosition.X < EndExitPosition.X &&
|
||||
!IsCloseToStart(wp.WorldPosition, minDistance) &&
|
||||
!IsCloseToEnd(wp.WorldPosition, minDistance)
|
||||
).ToList();
|
||||
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
|
||||
|
||||
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
|
||||
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
||||
@@ -3137,7 +3300,7 @@ namespace Barotrauma
|
||||
}
|
||||
tempSW.Stop();
|
||||
Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)");
|
||||
sub.SetPosition(spawnPoint, forceUndockFromStaticSubmarines: false);
|
||||
sub.SetPosition(spawnPoint);
|
||||
wreckPositions.Add(sub, positions);
|
||||
blockedRects.Add(sub, rects);
|
||||
return sub;
|
||||
@@ -3468,7 +3631,7 @@ namespace Barotrauma
|
||||
{
|
||||
locationType = LocationType.List.Where(lt =>
|
||||
outpostGenerationParams.AllowedLocationTypes.Any(allowedType =>
|
||||
allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom();
|
||||
allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(Rand.RandSync.Server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3649,9 +3812,10 @@ namespace Barotrauma
|
||||
}
|
||||
if (LevelData.IsBeaconActive)
|
||||
{
|
||||
if (reactorContainer != null && reactorContainer.Inventory.IsEmpty())
|
||||
if (reactorContainer != null && reactorContainer.Inventory.IsEmpty() &&
|
||||
reactorContainer.ContainableItemIdentifiers.Any() && ItemPrefab.Prefabs.ContainsKey(reactorContainer.ContainableItemIdentifiers.FirstOrDefault()))
|
||||
{
|
||||
ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItems[0].Identifiers[0]];
|
||||
ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItemIdentifiers.FirstOrDefault()];
|
||||
Spawner.AddToSpawnQueue(
|
||||
fuelPrefab, reactorContainer.Inventory,
|
||||
onSpawned: (it) => reactorComponent.PowerUpImmediately());
|
||||
@@ -3826,7 +3990,7 @@ namespace Barotrauma
|
||||
bool TryGetExtraSpawnPoint(out Vector2 point)
|
||||
{
|
||||
point = Vector2.Zero;
|
||||
var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom();
|
||||
var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom(Rand.RandSync.Unsynced);
|
||||
if (hull != null)
|
||||
{
|
||||
point = hull.WorldPosition;
|
||||
@@ -3891,12 +4055,39 @@ namespace Barotrauma
|
||||
LevelObjectManager = null;
|
||||
}
|
||||
|
||||
AbyssIslands?.Clear();
|
||||
AbyssResources?.Clear();
|
||||
Caves?.Clear();
|
||||
Tunnels?.Clear();
|
||||
PathPoints?.Clear();
|
||||
PositionsOfInterest?.Clear();
|
||||
|
||||
wreckPositions?.Clear();
|
||||
Wrecks?.Clear();
|
||||
|
||||
BeaconStation = null;
|
||||
beaconSonar = null;
|
||||
StartOutpost = null;
|
||||
EndOutpost = null;
|
||||
|
||||
blockedRects?.Clear();
|
||||
|
||||
EntitiesBeforeGenerate?.Clear();
|
||||
EqualityCheckValues?.Clear();
|
||||
|
||||
if (Ruins != null)
|
||||
{
|
||||
Ruins.Clear();
|
||||
Ruins = null;
|
||||
}
|
||||
|
||||
bottomPositions?.Clear();
|
||||
BottomBarrier = null;
|
||||
TopBarrier = null;
|
||||
SeaFloor = null;
|
||||
|
||||
distanceField = null;
|
||||
|
||||
if (ExtraWalls != null)
|
||||
{
|
||||
foreach (LevelWall w in ExtraWalls) { w.Dispose(); }
|
||||
@@ -3908,7 +4099,9 @@ namespace Barotrauma
|
||||
UnsyncedExtraWalls = null;
|
||||
}
|
||||
|
||||
tempCells?.Clear();
|
||||
cells = null;
|
||||
cellGrid = null;
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
@@ -3916,6 +4109,9 @@ namespace Barotrauma
|
||||
bodies = null;
|
||||
}
|
||||
|
||||
StartLocation = null;
|
||||
EndLocation = null;
|
||||
|
||||
Loaded = null;
|
||||
}
|
||||
|
||||
|
||||
+27
-10
@@ -84,25 +84,42 @@ namespace Barotrauma
|
||||
objectGrid = new List<LevelObject>[
|
||||
level.Size.X / GridSize,
|
||||
(level.Size.Y - level.BottomPos) / GridSize];
|
||||
|
||||
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
var levelCells = level.GetAllCells();
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(level.SeaFloor.Cells, LevelObjectPrefab.SpawnPosType.SeaFloor));
|
||||
|
||||
foreach (RuinGeneration.Ruin ruin in level.Ruins)
|
||||
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
foreach (var ruinShape in ruin.RuinShapes)
|
||||
if (!structure.HasBody || structure.HiddenInGame) { continue; }
|
||||
if (level.Ruins.Any(r => r.Submarine == structure.Submarine))
|
||||
{
|
||||
foreach (var wall in ruinShape.Walls)
|
||||
if (structure.IsHorizontal)
|
||||
{
|
||||
bool topHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitY * 64) != null;
|
||||
bool bottomHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitY * 64) != null;
|
||||
if (topHull && bottomHull ) { continue; }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(wall.A, wall.B),
|
||||
(wall.A + wall.B) / 2.0f - ruinShape.Center,
|
||||
new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)),
|
||||
bottomHull ? Vector2.UnitY : -Vector2.UnitY,
|
||||
LevelObjectPrefab.SpawnPosType.RuinWall,
|
||||
ruinShape.GetLineAlignment(wall)));
|
||||
bottomHull ? Alignment.Bottom : Alignment.Top));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rightHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitX * 64) != null;
|
||||
bool leftHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitX * 64) != null;
|
||||
if (rightHull && leftHull) { continue; }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(new Vector2(structure.WorldPosition.X, structure.WorldRect.Y), new Vector2(structure.WorldPosition.X, structure.WorldRect.Y - structure.WorldRect.Height)),
|
||||
leftHull ? Vector2.UnitX : -Vector2.UnitX,
|
||||
LevelObjectPrefab.SpawnPosType.RuinWall,
|
||||
leftHull ? Alignment.Left : Alignment.Right));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var posOfInterest in level.PositionsOfInterest)
|
||||
|
||||
+169
-119
@@ -13,7 +13,7 @@ namespace Barotrauma
|
||||
partial class LevelTrigger
|
||||
{
|
||||
[Flags]
|
||||
enum TriggererType
|
||||
public enum TriggererType
|
||||
{
|
||||
None = 0,
|
||||
Human = 1,
|
||||
@@ -258,13 +258,17 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type.");
|
||||
}
|
||||
UpdateCollisionCategories();
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.CollidesWith = GetCollisionCategories(triggeredBy);
|
||||
}
|
||||
|
||||
TriggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
|
||||
|
||||
var tagsArray = element.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in tagsArray)
|
||||
{
|
||||
tags.Add(tag.ToLower());
|
||||
tags.Add(tag.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (triggeredBy.HasFlag(TriggererType.OtherTrigger))
|
||||
@@ -272,30 +276,21 @@ namespace Barotrauma
|
||||
var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", new string[0]);
|
||||
foreach (string tag in otherTagsArray)
|
||||
{
|
||||
allowedOtherTriggerTags.Add(tag.ToLower());
|
||||
allowedOtherTriggerTags.Add(tag.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
string debugName = string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : $"LevelTrigger in {parentDebugName}";
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
statusEffects.Add(StatusEffect.Load(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in "+ parentDebugName));
|
||||
LoadStatusEffect(statusEffects, subElement, debugName);
|
||||
break;
|
||||
case "attack":
|
||||
case "damage":
|
||||
var attack = new Attack(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in " + parentDebugName);
|
||||
if (!triggerOnce)
|
||||
{
|
||||
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
|
||||
attack.Afflictions.Clear();
|
||||
foreach (Affliction affliction in multipliedAfflictions)
|
||||
{
|
||||
attack.Afflictions.Add(affliction, null);
|
||||
}
|
||||
}
|
||||
attacks.Add(attack);
|
||||
LoadAttack(subElement, debugName, triggerOnce, attacks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -304,16 +299,13 @@ namespace Barotrauma
|
||||
randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval);
|
||||
}
|
||||
|
||||
private void UpdateCollisionCategories()
|
||||
public static Category GetCollisionCategories(TriggererType triggeredBy)
|
||||
{
|
||||
if (PhysicsBody == null) return;
|
||||
|
||||
var collidesWith = Physics.CollisionNone;
|
||||
if (triggeredBy.HasFlag(TriggererType.Human) || triggeredBy.HasFlag(TriggererType.Creature)) { collidesWith |= Physics.CollisionCharacter; }
|
||||
if (triggeredBy.HasFlag(TriggererType.Item)) { collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile; }
|
||||
if (triggeredBy.HasFlag(TriggererType.Submarine)) { collidesWith |= Physics.CollisionWall; }
|
||||
|
||||
PhysicsBody.CollidesWith = collidesWith;
|
||||
return collidesWith;
|
||||
}
|
||||
|
||||
private void CalculateDirectionalForce()
|
||||
@@ -326,33 +318,31 @@ namespace Barotrauma
|
||||
-sa * unrotatedForce.X + ca * unrotatedForce.Y);
|
||||
}
|
||||
|
||||
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
|
||||
public static void LoadStatusEffect(List<StatusEffect> statusEffects, XElement element, string parentDebugName)
|
||||
{
|
||||
statusEffects.Add(StatusEffect.Load(element, parentDebugName));
|
||||
}
|
||||
|
||||
public static void LoadAttack(XElement element, string parentDebugName, bool triggerOnce, List<Attack> attacks)
|
||||
{
|
||||
var attack = new Attack(element, parentDebugName);
|
||||
if (!triggerOnce)
|
||||
{
|
||||
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
|
||||
attack.Afflictions.Clear();
|
||||
foreach (Affliction affliction in multipliedAfflictions)
|
||||
{
|
||||
attack.Afflictions.Add(affliction, null);
|
||||
}
|
||||
}
|
||||
attacks.Add(attack);
|
||||
}
|
||||
|
||||
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact)
|
||||
{
|
||||
Entity entity = GetEntity(fixtureB);
|
||||
if (entity == null) return false;
|
||||
|
||||
if (entity is Character character)
|
||||
{
|
||||
if (character.CurrentHull != null) return false;
|
||||
if (character.IsHuman)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Human)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Creature)) return false;
|
||||
}
|
||||
}
|
||||
else if (entity is Item item)
|
||||
{
|
||||
if (item.CurrentHull != null) return false;
|
||||
if (!triggeredBy.HasFlag(TriggererType.Item)) return false;
|
||||
}
|
||||
else if (entity is Submarine)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Submarine)) return false;
|
||||
}
|
||||
|
||||
if (entity == null) { return false; }
|
||||
if (!IsTriggeredByEntity(entity, triggeredBy, mustBeOutside: true)) { return false; }
|
||||
if (!triggerers.Contains(entity))
|
||||
{
|
||||
if (!IsTriggered)
|
||||
@@ -365,6 +355,34 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsTriggeredByEntity(Entity entity, TriggererType triggeredBy, bool mustBeOutside = false, (bool mustBe, Submarine sub) mustBeOnSpecificSub = default)
|
||||
{
|
||||
if (entity is Character character)
|
||||
{
|
||||
if (mustBeOutside && character.CurrentHull != null) { return false; }
|
||||
if (mustBeOnSpecificSub.mustBe && character.Submarine != mustBeOnSpecificSub.sub) { return false; }
|
||||
if (character.IsHuman)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Human)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Creature)) { return false; }
|
||||
}
|
||||
}
|
||||
else if (entity is Item item)
|
||||
{
|
||||
if (mustBeOutside && item.CurrentHull != null) { return false; }
|
||||
if (mustBeOnSpecificSub.mustBe && item.Submarine != mustBeOnSpecificSub.sub) { return false; }
|
||||
if (!triggeredBy.HasFlag(TriggererType.Item)) { return false; }
|
||||
}
|
||||
else if (entity is Submarine)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Submarine)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
|
||||
{
|
||||
Entity entity = GetEntity(fixtureB);
|
||||
@@ -379,10 +397,21 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (CheckContactsForOtherFixtures(PhysicsBody, fixtureB, entity)) { return; }
|
||||
|
||||
if (triggerers.Contains(entity))
|
||||
{
|
||||
TriggererPosition.Remove(entity);
|
||||
triggerers.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckContactsForOtherFixtures(PhysicsBody triggerBody, Fixture otherFixture, Entity separatingEntity)
|
||||
{
|
||||
//check if there are contacts with any other fixture of the trigger
|
||||
//(the OnSeparation callback happens when two fixtures separate,
|
||||
//e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
|
||||
foreach (Fixture fixture in PhysicsBody.FarseerBody.FixtureList)
|
||||
foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList)
|
||||
{
|
||||
ContactEdge contactEdge = fixture.Body.ContactList;
|
||||
while (contactEdge != null)
|
||||
@@ -393,30 +422,24 @@ namespace Barotrauma
|
||||
{
|
||||
if (contactEdge.Contact.FixtureA != fixture && contactEdge.Contact.FixtureB != fixture)
|
||||
{
|
||||
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
|
||||
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == otherFixture ?
|
||||
contactEdge.Contact.FixtureB :
|
||||
contactEdge.Contact.FixtureA);
|
||||
if (otherEntity == entity) { return; }
|
||||
if (otherEntity == separatingEntity) { return true; }
|
||||
}
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerers.Contains(entity))
|
||||
{
|
||||
TriggererPosition.Remove(entity);
|
||||
triggerers.Remove(entity);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Entity GetEntity(Fixture fixture)
|
||||
public static Entity GetEntity(Fixture fixture)
|
||||
{
|
||||
if (fixture.Body == null || fixture.Body.UserData == null) { return null; }
|
||||
if (fixture.Body.UserData is Entity entity) { return entity; }
|
||||
if (fixture.Body.UserData is Limb limb) { return limb.character; }
|
||||
if (fixture.Body.UserData is SubmarineBody subBody) { return subBody.Submarine; }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -452,15 +475,7 @@ namespace Barotrauma
|
||||
|
||||
triggerers.RemoveWhere(t => t.Removed);
|
||||
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
//failsafe to ensure triggerers get removed when they're far from the trigger
|
||||
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(PhysicsBody.GetMaxExtent() * 5), 5000.0f);
|
||||
triggerers.RemoveWhere(t =>
|
||||
{
|
||||
return Vector2.Distance(t.WorldPosition, WorldPosition) > maxExtent;
|
||||
});
|
||||
}
|
||||
RemoveDistantTriggerers(PhysicsBody, triggerers, WorldPosition);
|
||||
|
||||
bool isNotClient = true;
|
||||
#if CLIENT
|
||||
@@ -525,57 +540,15 @@ namespace Barotrauma
|
||||
|
||||
foreach (Entity triggerer in triggerers)
|
||||
{
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (effect.type == ActionType.OnBroken) { continue; }
|
||||
Vector2? position = null;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; }
|
||||
if (triggerer is Character character)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, character, position);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
|
||||
{
|
||||
foreach (Item item in character.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.ContainedItems == null) { continue; }
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (triggerer is Item item)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
ApplyStatusEffects(statusEffects, worldPosition, triggerer, deltaTime, targets);
|
||||
|
||||
if (triggerer is IDamageable damageable)
|
||||
{
|
||||
foreach (Attack attack in attacks)
|
||||
{
|
||||
attack.DoDamage(null, damageable, WorldPosition, deltaTime, false);
|
||||
}
|
||||
ApplyAttacks(attacks, damageable, worldPosition, deltaTime);
|
||||
}
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
foreach (Attack attack in attacks)
|
||||
{
|
||||
float structureDamage = attack.GetStructureDamage(deltaTime);
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyAttacks(attacks, worldPosition, deltaTime);
|
||||
if (!string.IsNullOrWhiteSpace(InfectIdentifier))
|
||||
{
|
||||
submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance);
|
||||
@@ -586,16 +559,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (triggerer is Character character)
|
||||
{
|
||||
ApplyForce(character.AnimController.Collider, deltaTime);
|
||||
ApplyForce(character.AnimController.Collider);
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
ApplyForce(limb.body, deltaTime);
|
||||
ApplyForce(limb.body);
|
||||
}
|
||||
}
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
ApplyForce(submarine.SubBody.Body, deltaTime);
|
||||
ApplyForce(submarine.SubBody.Body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,12 +579,84 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyForce(PhysicsBody body, float deltaTime)
|
||||
public static void RemoveDistantTriggerers(PhysicsBody physicsBody, HashSet<Entity> triggerers, Vector2 calculateDistanceTo)
|
||||
{
|
||||
//failsafe to ensure triggerers get removed when they're far from the trigger
|
||||
if (physicsBody == null) { return; }
|
||||
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(physicsBody.GetMaxExtent() * 5), 5000.0f);
|
||||
triggerers.RemoveWhere(t =>
|
||||
{
|
||||
return Vector2.Distance(t.WorldPosition, calculateDistanceTo) > maxExtent;
|
||||
});
|
||||
}
|
||||
|
||||
public static void ApplyStatusEffects(List<StatusEffect> statusEffects, Vector2 worldPosition, Entity triggerer, float deltaTime, List<ISerializableEntity> targets)
|
||||
{
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (effect.type == ActionType.OnBroken) { return; }
|
||||
Vector2? position = null;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = worldPosition; }
|
||||
if (triggerer is Character character)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, character, position);
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null)
|
||||
{
|
||||
foreach (Item item in character.Inventory.AllItemsMod)
|
||||
{
|
||||
if (item.ContainedItems == null) { continue; }
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (triggerer is Item item)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies attacks to a damageable.
|
||||
/// </summary>
|
||||
public static void ApplyAttacks(List<Attack> attacks, IDamageable damageable, Vector2 worldPosition, float deltaTime)
|
||||
{
|
||||
foreach (Attack attack in attacks)
|
||||
{
|
||||
attack.DoDamage(null, damageable, worldPosition, deltaTime, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies attacks to structures.
|
||||
/// </summary>
|
||||
public static void ApplyAttacks(List<Attack> attacks, Vector2 worldPosition, float deltaTime)
|
||||
{
|
||||
foreach (Attack attack in attacks)
|
||||
{
|
||||
float structureDamage = attack.GetStructureDamage(deltaTime);
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyForce(PhysicsBody body)
|
||||
{
|
||||
float distFactor = 1.0f;
|
||||
if (ForceFalloff)
|
||||
{
|
||||
distFactor = 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(body.SimPosition, PhysicsBody.SimPosition)) / ColliderRadius;
|
||||
distFactor = GetDistanceFactor(body, PhysicsBody, ColliderRadius);
|
||||
if (distFactor < 0.0f) return;
|
||||
}
|
||||
|
||||
@@ -621,19 +666,19 @@ namespace Barotrauma
|
||||
if (ForceVelocityLimit < 1000.0f)
|
||||
body.ApplyForce(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
|
||||
else
|
||||
body.ApplyForce(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
body.ApplyForce(Force * currentForceFluctuation * distFactor);
|
||||
break;
|
||||
case TriggerForceMode.Acceleration:
|
||||
if (ForceVelocityLimit < 1000.0f)
|
||||
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, ForceVelocityLimit);
|
||||
else
|
||||
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor);
|
||||
break;
|
||||
case TriggerForceMode.Impulse:
|
||||
if (ForceVelocityLimit < 1000.0f)
|
||||
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: ForceVelocityLimit);
|
||||
else
|
||||
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor);
|
||||
break;
|
||||
case TriggerForceMode.LimitVelocity:
|
||||
float maxVel = ForceVelocityLimit * currentForceFluctuation * distFactor;
|
||||
@@ -648,6 +693,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius)
|
||||
{
|
||||
return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition)) / colliderRadius;
|
||||
}
|
||||
|
||||
public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
|
||||
{
|
||||
Vector2 baseVel = GetWaterFlowVelocity();
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
/// <summary>
|
||||
/// nodes of a binary tree used for generating underwater "dungeons"
|
||||
/// </summary>
|
||||
class BTRoom : RuinShape
|
||||
{
|
||||
private BTRoom[] subRooms;
|
||||
|
||||
public BTRoom Parent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Corridor Corridor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BTRoom[] SubRooms
|
||||
{
|
||||
get { return subRooms; }
|
||||
}
|
||||
|
||||
public BTRoom Adjacent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public BTRoom(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
}
|
||||
|
||||
public void Split(float minDivRatio, float verticalProbability = 0.5f, int minWidth = 200, int minHeight = 200)
|
||||
{
|
||||
bool verticalSplit = Rand.Range(0.0f, rect.Height / (float)rect.Width, Rand.RandSync.Server) < verticalProbability;
|
||||
if (rect.Width * minDivRatio < minWidth && rect.Height * minDivRatio < minHeight)
|
||||
{
|
||||
minDivRatio = 0.5f;
|
||||
}
|
||||
else if (rect.Width * minDivRatio < minWidth)
|
||||
{
|
||||
verticalSplit = false;
|
||||
}
|
||||
else if (rect.Height * minDivRatio < minHeight)
|
||||
{
|
||||
verticalSplit = true;
|
||||
}
|
||||
|
||||
subRooms = new BTRoom[2];
|
||||
if (verticalSplit)
|
||||
{
|
||||
SplitVertical(minDivRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
SplitHorizontal(minDivRatio);
|
||||
}
|
||||
|
||||
subRooms[0].Parent = this;
|
||||
subRooms[1].Parent = this;
|
||||
|
||||
subRooms[0].Adjacent = subRooms[1];
|
||||
subRooms[1].Adjacent = subRooms[0];
|
||||
}
|
||||
|
||||
private void SplitHorizontal(float minDivRatio)
|
||||
{
|
||||
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
|
||||
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, rect.Width, (int)(rect.Height * div)));
|
||||
subRooms[1] = new BTRoom(new Rectangle(rect.X, rect.Y + subRooms[0].rect.Height, rect.Width, rect.Height - subRooms[0].rect.Height));
|
||||
|
||||
}
|
||||
|
||||
private void SplitVertical(float minDivRatio)
|
||||
{
|
||||
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
|
||||
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, (int)(rect.Width * div), rect.Height));
|
||||
subRooms[1] = new BTRoom(new Rectangle(rect.X + subRooms[0].rect.Width, rect.Y, rect.Width - subRooms[0].rect.Width, rect.Height));
|
||||
}
|
||||
|
||||
public override void CreateWalls()
|
||||
{
|
||||
Walls = new List<Line>
|
||||
{
|
||||
new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)),
|
||||
new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)),
|
||||
new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)),
|
||||
new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom))
|
||||
};
|
||||
}
|
||||
|
||||
public void Scale(Vector2 scale)
|
||||
{
|
||||
rect.Inflate((scale.X - 1.0f) * 0.5f * rect.Width, (scale.Y - 1.0f) * 0.5f * rect.Height);
|
||||
}
|
||||
|
||||
public List<BTRoom> GetLeaves()
|
||||
{
|
||||
return GetLeaves(new List<BTRoom>());
|
||||
}
|
||||
|
||||
private List<BTRoom> GetLeaves(List<BTRoom> leaves)
|
||||
{
|
||||
if (subRooms == null)
|
||||
{
|
||||
leaves.Add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
subRooms[0].GetLeaves(leaves);
|
||||
subRooms[1].GetLeaves(leaves);
|
||||
}
|
||||
|
||||
return leaves;
|
||||
}
|
||||
|
||||
public void GenerateCorridors(int minWidth, int maxWidth, List<Corridor> corridors)
|
||||
{
|
||||
if (Adjacent != null && Corridor == null)
|
||||
{
|
||||
Corridor = new Corridor(this, Rand.Range(minWidth, maxWidth, Rand.RandSync.Server), corridors);
|
||||
}
|
||||
|
||||
if (subRooms != null)
|
||||
{
|
||||
subRooms[0].GenerateCorridors(minWidth, maxWidth, corridors);
|
||||
subRooms[1].GenerateCorridors(minWidth, maxWidth, corridors);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<BTRoom> rooms, List<Corridor> corridors)
|
||||
{
|
||||
entrance.CalculateDistanceFromEntrance(0, rooms, new List<Corridor>(corridors));
|
||||
}
|
||||
|
||||
private void CalculateDistanceFromEntrance(int currentDist, List<BTRoom> rooms, List<Corridor> corridors)
|
||||
{
|
||||
DistanceFromEntrance = DistanceFromEntrance == 0 ? currentDist : Math.Min(currentDist, DistanceFromEntrance);
|
||||
|
||||
currentDist++;
|
||||
|
||||
var roomRect = Rect;
|
||||
roomRect.Inflate(5, 5);
|
||||
foreach (var corridor in corridors)
|
||||
{
|
||||
var corridorRect = corridor.Rect;
|
||||
corridorRect.Inflate(5, 5);
|
||||
if (!corridorRect.Intersects(roomRect)) continue;
|
||||
|
||||
corridor.DistanceFromEntrance = corridor.DistanceFromEntrance == 0 ?
|
||||
DistanceFromEntrance + 1 :
|
||||
Math.Min(corridor.DistanceFromEntrance, DistanceFromEntrance + 1);
|
||||
|
||||
|
||||
List<BTRoom> connectedRooms = new List<BTRoom>();
|
||||
foreach (var otherRoom in rooms)
|
||||
{
|
||||
if (otherRoom == this) continue;
|
||||
if (otherRoom.DistanceFromEntrance > 0 && otherRoom.DistanceFromEntrance < currentDist) continue;
|
||||
|
||||
var otherRoomRect = otherRoom.Rect;
|
||||
otherRoomRect.Inflate(5, 5);
|
||||
if (corridorRect.Intersects(otherRoomRect)) { connectedRooms.Add(otherRoom); }
|
||||
}
|
||||
|
||||
connectedRooms.Sort((r1, r2) =>
|
||||
{
|
||||
return
|
||||
(Math.Abs(r1.Rect.Center.X - Rect.Center.X) + Math.Abs(r1.Rect.Center.Y - Rect.Center.Y)) -
|
||||
(Math.Abs(r2.Rect.Center.X - Rect.Center.X) + Math.Abs(r2.Rect.Center.Y - Rect.Center.Y));
|
||||
});
|
||||
|
||||
for (int i = 0; i < connectedRooms.Count; i++)
|
||||
{
|
||||
connectedRooms[i].CalculateDistanceFromEntrance(currentDist + 1 + i, rooms, corridors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
|
||||
class Corridor : RuinShape
|
||||
{
|
||||
private readonly bool isHorizontal;
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
}
|
||||
|
||||
public BTRoom[] ConnectedRooms
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Corridor(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
|
||||
isHorizontal = rect.Width > rect.Height;
|
||||
}
|
||||
|
||||
public Corridor(BTRoom room, int width, List<Corridor> corridors)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(room.Adjacent != null);
|
||||
|
||||
ConnectedRooms = new BTRoom[2];
|
||||
ConnectedRooms[0] = room;
|
||||
ConnectedRooms[1] = room.Adjacent;
|
||||
|
||||
Rectangle room1, room2;
|
||||
|
||||
room1 = room.Rect;
|
||||
room2 = room.Adjacent.Rect;
|
||||
|
||||
isHorizontal = (room1.Right <= room2.X || room2.Right <= room1.X);
|
||||
|
||||
//use the leaves as starting points for the corridor
|
||||
if (room.SubRooms != null)
|
||||
{
|
||||
var leaves1 = room.GetLeaves();
|
||||
var leaves2 = room.Adjacent.GetLeaves();
|
||||
|
||||
var suitableLeaves = GetSuitableLeafRooms(leaves1, leaves2, width, isHorizontal);
|
||||
if (suitableLeaves == null || suitableLeaves.Length < 2)
|
||||
{
|
||||
// No suitable leaves found due to intersections
|
||||
//DebugConsole.ThrowError("Error while generating ruins. Could not find a suitable position for a corridor. The width of the corridors may be too large compared to the sizes of the rooms.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectedRooms[0] = suitableLeaves[0];
|
||||
ConnectedRooms[1] = suitableLeaves[1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rect = CalculateRectangle(room1, room2, width, isHorizontal);
|
||||
if (rect.Width <= 0 || rect.Height <= 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while generating ruins. Attempted to create a corridor with a width or height of <= 0");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
room.Corridor = this;
|
||||
room.Adjacent.Corridor = this;
|
||||
|
||||
for (int i = corridors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var corridor = corridors[i];
|
||||
|
||||
if (corridor.rect.Intersects(this.rect))
|
||||
{
|
||||
if (isHorizontal && corridor.isHorizontal)
|
||||
{
|
||||
if (this.rect.Width < corridor.rect.Width)
|
||||
return;
|
||||
else
|
||||
corridors.RemoveAt(i);
|
||||
}
|
||||
else if (!isHorizontal && !corridor.isHorizontal)
|
||||
{
|
||||
if (this.rect.Height < corridor.rect.Height)
|
||||
return;
|
||||
else
|
||||
corridors.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
corridors.Add(this);
|
||||
}
|
||||
|
||||
public override void CreateWalls()
|
||||
{
|
||||
Walls = new List<Line>();
|
||||
if (IsHorizontal)
|
||||
{
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)));
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)));
|
||||
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find two rooms which have two face-two-face walls that we can place a corridor in between
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private BTRoom[] GetSuitableLeafRooms(List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
|
||||
{
|
||||
int iOffset = Rand.Int(leaves1.Count, Rand.RandSync.Server);
|
||||
int jOffset = Rand.Int(leaves2.Count, Rand.RandSync.Server);
|
||||
|
||||
for (int iCount = 0; iCount < leaves1.Count; iCount++)
|
||||
{
|
||||
int i = (iCount + iOffset) % leaves1.Count;
|
||||
|
||||
for (int jCount = 0; jCount < leaves2.Count; jCount++)
|
||||
{
|
||||
int j = (jCount + jOffset) % leaves2.Count;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom - width) continue;
|
||||
if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y + width) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (leaves1[i].Rect.X > leaves2[j].Rect.Right - width) continue;
|
||||
if (leaves1[i].Rect.Right < leaves2[j].Rect.X + width) continue;
|
||||
}
|
||||
|
||||
// Check if the given corridor rect would intersect over a third room
|
||||
if (CheckForIntersection(leaves1[i], leaves2[j], leaves1, leaves2, width, isHorizontal)) continue;
|
||||
|
||||
return new BTRoom[] { leaves1[i], leaves2[j] };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool CheckForIntersection(BTRoom potential1, BTRoom potential2, List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
|
||||
{
|
||||
Rectangle potentialCorridorRectangle = CalculateRectangle(potential1.Rect, potential2.Rect, width, isHorizontal);
|
||||
|
||||
if (potentialCorridorRectangle.Width <= 0 || potentialCorridorRectangle.Height <= 0) return true; // Invalid rectangle
|
||||
|
||||
for (int i = 0; i < leaves1.Count; i++)
|
||||
{
|
||||
if (leaves1[i] == potential1) continue;
|
||||
if (potentialCorridorRectangle.Intersects(leaves1[i].Rect)) return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < leaves2.Count; i++)
|
||||
{
|
||||
if (leaves2[i] == potential2) continue;
|
||||
if (potentialCorridorRectangle.Intersects(leaves2[i].Rect)) return true;
|
||||
}
|
||||
|
||||
rect = potentialCorridorRectangle; // Save the rectangle that passes the test
|
||||
return false;
|
||||
}
|
||||
|
||||
private Rectangle CalculateRectangle(Rectangle rect1, Rectangle rect2, int width, bool isHorizontal)
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
int left = Math.Min(rect1.Right, rect2.Right);
|
||||
int right = Math.Max(rect1.X, rect2.X);
|
||||
|
||||
int top = Math.Max(rect1.Y, rect2.Y);
|
||||
//int bottom = Math.Min(room1.Bottom, room2.Bottom);
|
||||
int yPos = top;//Rand.Range(top, bottom - width, Rand.RandSync.Server);
|
||||
|
||||
return new Rectangle(left, yPos, right - left, width);
|
||||
}
|
||||
else if (rect1.Y > rect2.Bottom || rect2.Y > rect1.Bottom)
|
||||
{
|
||||
int left = Math.Max(rect1.X, rect2.X);
|
||||
int right = Math.Min(rect1.Right, rect2.Right);
|
||||
|
||||
int top = Math.Min(rect1.Bottom, rect2.Bottom);
|
||||
int bottom = Math.Max(rect1.Y, rect2.Y);
|
||||
|
||||
int xPos = Rand.Range(left, right - width, Rand.RandSync.Server);
|
||||
|
||||
return new Rectangle(xPos, top, width, bottom - top);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("wat");
|
||||
return new Rectangle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-410
@@ -18,9 +18,9 @@ namespace Barotrauma.RuinGeneration
|
||||
Wall, Back, Door, Hatch, Prop
|
||||
}
|
||||
|
||||
class RuinGenerationParams : ISerializableEntity
|
||||
class RuinGenerationParams : OutpostGenerationParams
|
||||
{
|
||||
public static List<RuinGenerationParams> List
|
||||
public static List<RuinGenerationParams> RuinParams
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -34,115 +34,27 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
private static List<RuinGenerationParams> paramsList;
|
||||
|
||||
private string filePath;
|
||||
|
||||
private readonly List<RuinRoom> roomTypeList;
|
||||
|
||||
public string Name => "RuinGenerationParams";
|
||||
private readonly string filePath;
|
||||
|
||||
public override string Name => "RuinGenerationParams";
|
||||
|
||||
[Serialize("5000,5000", false), Editable]
|
||||
public Point SizeMin
|
||||
|
||||
private RuinGenerationParams(XElement element, string filePath) : base(element, filePath)
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize("8000,8000", false), Editable]
|
||||
public Point SizeMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
[Serialize(3, false, description: "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the minimum number of times the split is done."), Editable(MinValueInt = 1, MaxValueInt = 10)]
|
||||
public int RoomDivisionIterationsMin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(4, false, description: "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the maximum number of times the split is done."), Editable(MinValueInt = 1, MaxValueInt = 10)]
|
||||
public int RoomDivisionIterationsMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.5f, false, description: "The probability for the split algorithm to split the area vertically. High values tend to create tall, vertical rooms, and low values wide, horizontal rooms."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.9f)]
|
||||
public float VerticalSplitProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(400, false, description: "The splitting algorithm attempts to keep the width of the split areas larger than this. If the width of the split areas would be smaller than this after a vertical split, the algorithm would do a horizontal split."), Editable]
|
||||
public int MinSplitWidth
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize(400, false, description: "The splitting algorithm attempts to keep the height of the split areas larger than this. If the height of the split areas would be smaller than this after a vertical split, the algorithm would do a horizontal split."), Editable]
|
||||
public int MinSplitHeight
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0.5,0.9", false, description: "The minimum and maximum width of a room relative to the areas created by the split algorithm."), Editable]
|
||||
public Vector2 RoomWidthRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize("0.5,0.9", false, description: "The minimum and maximum height of a room relative to the areas created by the split algorithm."), Editable]
|
||||
public Vector2 RoomHeightRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("200,256", false, description: "The minimum and maximum width of the corridors between rooms."), Editable]
|
||||
public Point CorridorWidthRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<string, SerializableProperty>();
|
||||
|
||||
public IEnumerable<RuinRoom> RoomTypeList
|
||||
{
|
||||
get { return roomTypeList; }
|
||||
}
|
||||
|
||||
private RuinGenerationParams(XElement element)
|
||||
{
|
||||
roomTypeList = new List<RuinRoom>();
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
roomTypeList.Add(new RuinRoom(subElement));
|
||||
}
|
||||
}
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public static RuinGenerationParams GetRandom()
|
||||
public static RuinGenerationParams GetRandom(Rand.RandSync randSync = Rand.RandSync.Server)
|
||||
{
|
||||
if (paramsList == null) { LoadAll(); }
|
||||
|
||||
if (paramsList.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("No ruin configuration files found in any content package.");
|
||||
return new RuinGenerationParams(null);
|
||||
return new RuinGenerationParams(null, null);
|
||||
}
|
||||
|
||||
return paramsList[Rand.Int(paramsList.Count, Rand.RandSync.Server)];
|
||||
return paramsList[Rand.Int(paramsList.Count, randSync)];
|
||||
}
|
||||
|
||||
private static void LoadAll()
|
||||
@@ -151,23 +63,24 @@ namespace Barotrauma.RuinGeneration
|
||||
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
if (doc?.Root == null) { continue; }
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
paramsList.Clear();
|
||||
DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow);
|
||||
var mainElement = subElement;
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
mainElement = subElement.FirstElement();
|
||||
paramsList.Clear();
|
||||
DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow);
|
||||
}
|
||||
else if (paramsList.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'");
|
||||
}
|
||||
var newParams = new RuinGenerationParams(mainElement, configFile.Path);
|
||||
paramsList.Add(newParams);
|
||||
}
|
||||
else if (paramsList.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'");
|
||||
}
|
||||
var newParams = new RuinGenerationParams(mainElement)
|
||||
{
|
||||
filePath = configFile.Path
|
||||
};
|
||||
paramsList.Add(newParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,11 +98,11 @@ namespace Barotrauma.RuinGeneration
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
foreach (RuinGenerationParams generationParams in List)
|
||||
foreach (RuinGenerationParams generationParams in RuinParams)
|
||||
{
|
||||
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
|
||||
{
|
||||
if (configFile.Path != generationParams.filePath) continue;
|
||||
if (configFile.Path != generationParams.filePath) { continue; }
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc == null) { continue; }
|
||||
@@ -205,298 +118,4 @@ namespace Barotrauma.RuinGeneration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RuinRoom : ISerializableEntity
|
||||
{
|
||||
public enum RoomPlacement
|
||||
{
|
||||
Any,
|
||||
First,
|
||||
Last
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float Commonness { get; private set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<string, SerializableProperty>();
|
||||
|
||||
[Serialize(RoomPlacement.Any, false), Editable]
|
||||
public RoomPlacement Placement
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, false), Editable]
|
||||
public int PlacementOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false), Editable]
|
||||
public bool IsCorridor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false), Editable]
|
||||
public float MinWaterAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize(1.0f, false), Editable]
|
||||
public float MaxWaterAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private List<RuinEntityConfig> entityList = new List<RuinEntityConfig>();
|
||||
|
||||
public RuinRoom(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
Name = element.GetAttributeString("name", "");
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
int groupIndex = 0;
|
||||
LoadEntities(element, ref groupIndex);
|
||||
}
|
||||
|
||||
void LoadEntities(XElement element2, ref int groupIndex)
|
||||
{
|
||||
foreach (XElement subElement in element2.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("chooseone", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
groupIndex++;
|
||||
LoadEntities(subElement, ref groupIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
entityList.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RuinEntityConfig GetRandomEntity(RuinEntityType type, Alignment alignment)
|
||||
{
|
||||
var matchingEntities = entityList.FindAll(rs =>
|
||||
rs.Type == type &&
|
||||
rs.Alignment.HasFlag(alignment));
|
||||
|
||||
if (!matchingEntities.Any()) return null;
|
||||
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
matchingEntities,
|
||||
matchingEntities.Select(s => s.Commonness).ToList(),
|
||||
Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
public List<RuinEntityConfig> GetPropList(RuinShape room, Rand.RandSync randSync)
|
||||
{
|
||||
Dictionary<int, List<RuinEntityConfig>> propGroups = new Dictionary<int, List<RuinEntityConfig>>();
|
||||
foreach (RuinEntityConfig entityConfig in entityList)
|
||||
{
|
||||
if (entityConfig.Type != RuinEntityType.Prop) { continue; }
|
||||
if (room.Rect.Width < entityConfig.MinRoomSize.X || room.Rect.Height < entityConfig.MinRoomSize.Y) { continue; }
|
||||
if (room.Rect.Width > entityConfig.MaxRoomSize.X || room.Rect.Height > entityConfig.MaxRoomSize.Y) { continue; }
|
||||
if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex))
|
||||
{
|
||||
propGroups[entityConfig.SingleGroupIndex] = new List<RuinEntityConfig>();
|
||||
}
|
||||
propGroups[entityConfig.SingleGroupIndex].Add(entityConfig);
|
||||
}
|
||||
|
||||
List<RuinEntityConfig> props = new List<RuinEntityConfig>();
|
||||
foreach (KeyValuePair<int, List<RuinEntityConfig>> propGroup in propGroups)
|
||||
{
|
||||
if (propGroup.Key == 0)
|
||||
{
|
||||
props.AddRange(propGroup.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
class RuinEntityConfig : ISerializableEntity
|
||||
{
|
||||
public readonly MapEntityPrefab Prefab;
|
||||
|
||||
public enum RelativePlacement
|
||||
{
|
||||
SameRoom,
|
||||
NextRoom,
|
||||
NextCorridor,
|
||||
PreviousRoom,
|
||||
PreviousCorridor,
|
||||
FirstRoom,
|
||||
FirstCorridor,
|
||||
LastRoom,
|
||||
LastCorridor
|
||||
}
|
||||
|
||||
public class EntityConnection
|
||||
{
|
||||
//which type of room to search for the item to connect to
|
||||
//sameroom, nextroom, previousroom, firstroom and lastroom are also valid
|
||||
public string RoomName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string TargetEntityIdentifier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//Identifier of the item to run the wire from. Only needed in item assemblies to determine which item in the assembly to use.
|
||||
public string SourceEntityIdentifier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//if set, the connection is done by running a wire from
|
||||
//(Pair.First = the name of the connection in this item) to (Pair.Second = the name of the connection in the target item)
|
||||
public Pair<string, string> WireConnection
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public EntityConnection(XElement element)
|
||||
{
|
||||
RoomName = element.GetAttributeString("roomname", "");
|
||||
TargetEntityIdentifier = element.GetAttributeString("targetentity", "");
|
||||
SourceEntityIdentifier = element.GetAttributeString("sourceentity", "");
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("wire", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
WireConnection = new Pair<string, string>(
|
||||
subElement.GetAttributeString("from", ""),
|
||||
subElement.GetAttributeString("to", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(Alignment.Bottom, false), Editable]
|
||||
public Alignment Alignment { get; private set; }
|
||||
|
||||
[Serialize("0,0", false, description: "Minimum offset from the anchor position, relative to the size of the room." +
|
||||
" For example, a value of { -0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-left corner of the room and bottom-center."), Editable]
|
||||
public Vector2 MinOffset { get; private set; }
|
||||
[Serialize("0,0", false, description: "Maximum offset from the anchor position, relative to the size of the room." +
|
||||
" For example, a value of { 0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-right corner of the room and bottom-center."), Editable]
|
||||
public Vector2 MaxOffset { get; private set; }
|
||||
|
||||
[Serialize(RuinEntityType.Prop, false), Editable]
|
||||
public RuinEntityType Type { get; private set; }
|
||||
|
||||
[Serialize(false, false), Editable]
|
||||
public bool Expand { get; private set; }
|
||||
|
||||
[Serialize(RelativePlacement.SameRoom, false), Editable]
|
||||
public RelativePlacement PlacementRelativeToParent { get; private set; }
|
||||
|
||||
[Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float Commonness { get; private set; }
|
||||
|
||||
[Serialize(1, false)]
|
||||
public int MinAmount { get; private set; }
|
||||
[Serialize(1, false)]
|
||||
public int MaxAmount { get; private set; }
|
||||
|
||||
[Serialize("0,0", false)]
|
||||
public Point MinRoomSize { get; private set; }
|
||||
|
||||
[Serialize("100000,100000", false)]
|
||||
public Point MaxRoomSize { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string TargetContainer { get; private set; }
|
||||
|
||||
public List<EntityConnection> EntityConnections { get; private set; } = new List<EntityConnection>();
|
||||
|
||||
|
||||
public int SingleGroupIndex;
|
||||
|
||||
private readonly List<RuinEntityConfig> childEntities = new List<RuinEntityConfig>();
|
||||
|
||||
public IEnumerable<RuinEntityConfig> ChildEntities
|
||||
{
|
||||
get { return childEntities; }
|
||||
}
|
||||
|
||||
public string Name => Prefab == null ? "null" : Prefab.Name;
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<string, SerializableProperty>();
|
||||
|
||||
public RuinEntityConfig(XElement element)
|
||||
{
|
||||
string name = element.GetAttributeString("prefab", "");
|
||||
Prefab = MapEntityPrefab.Find(name: null, identifier: name);
|
||||
|
||||
if (Prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Loading ruin entity config failed - map entity prefab \"" + name + "\" not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
int gIndex = 0;
|
||||
LoadChildren(element, ref gIndex);
|
||||
|
||||
void LoadChildren(XElement element2, ref int groupIndex)
|
||||
{
|
||||
foreach (XElement subElement in element2.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "connection":
|
||||
case "entityconnection":
|
||||
EntityConnections.Add(new EntityConnection(subElement));
|
||||
break;
|
||||
case "chooseone":
|
||||
groupIndex++;
|
||||
LoadChildren(subElement, ref groupIndex);
|
||||
break;
|
||||
default:
|
||||
childEntities.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,7 @@ namespace Barotrauma
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
public bool Discovered;
|
||||
public bool Discovered { get; private set; }
|
||||
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
@@ -90,7 +90,7 @@ namespace Barotrauma
|
||||
|
||||
private const float StoreMaxReputationModifier = 0.1f;
|
||||
private const float StoreSellPriceModifier = 0.8f;
|
||||
private const float DailySpecialPriceModifier = 0.9f;
|
||||
private const float DailySpecialPriceModifier = 0.5f;
|
||||
private const float RequestGoodPriceModifier = 1.5f;
|
||||
public const int StoreInitialBalance = 5000;
|
||||
/// <summary>
|
||||
@@ -868,6 +868,8 @@ namespace Barotrauma
|
||||
// Adjust by random price modifier
|
||||
price = ((100 + StorePriceModifier) / 100.0f) * price;
|
||||
|
||||
price *= priceInfo.BuyingPriceMultiplier;
|
||||
|
||||
// Adjust by daily special status
|
||||
if (considerDailySpecials && DailySpecials.Contains(item))
|
||||
{
|
||||
@@ -1004,12 +1006,22 @@ namespace Barotrauma
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
StoreStock = stock;
|
||||
|
||||
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval)
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
|
||||
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval ||
|
||||
DailySpecials.Count() != DailySpecialsCount + extraSpecialSalesCount)
|
||||
{
|
||||
CreateStoreSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetExtraSpecialSalesCount()
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters();
|
||||
if (!characters.Any()) { return 0; }
|
||||
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
private void GenerateRandomPriceModifier()
|
||||
{
|
||||
StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange);
|
||||
@@ -1033,7 +1045,9 @@ namespace Barotrauma
|
||||
}
|
||||
availableStock.Add(stockItem.ItemPrefab, weight);
|
||||
}
|
||||
for (int i = 0; i < DailySpecialsCount; i++)
|
||||
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
for (int i = 0; i < DailySpecialsCount + extraSpecialSalesCount; i++)
|
||||
{
|
||||
if (availableStock.None()) { break; }
|
||||
var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced);
|
||||
@@ -1111,6 +1125,30 @@ namespace Barotrauma
|
||||
return nextStatus;
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
{
|
||||
if (Discovered) { return; }
|
||||
Discovered = true;
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters().ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Abilities.AbilityLocation(this)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (Type != OriginalType)
|
||||
{
|
||||
ChangeType(OriginalType);
|
||||
PendingLocationTypeChange = null;
|
||||
}
|
||||
CreateStore(force: true);
|
||||
ClearMissions();
|
||||
LevelData?.EventHistory?.Clear();
|
||||
UnlockInitialMissions();
|
||||
Discovered = false;
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
{
|
||||
var locationElement = new XElement("location",
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace Barotrauma
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
|
||||
CurrentLocation.Discovered = true;
|
||||
CurrentLocation.Discover(true);
|
||||
CurrentLocation.CreateStore();
|
||||
|
||||
InitProjectSpecific();
|
||||
@@ -472,17 +472,18 @@ namespace Barotrauma
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
|
||||
float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width);
|
||||
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
|
||||
}
|
||||
|
||||
AssignBiomes();
|
||||
CreateEndLocation();
|
||||
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location)
|
||||
{
|
||||
Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f)
|
||||
Difficulty = MathHelper.Clamp(GetLevelDifficulty(location.MapPosition.X / Width), 0.0f, 100.0f)
|
||||
};
|
||||
location.UnlockInitialMissions();
|
||||
}
|
||||
@@ -490,6 +491,14 @@ namespace Barotrauma
|
||||
{
|
||||
connection.LevelData = new LevelData(connection);
|
||||
}
|
||||
|
||||
float GetLevelDifficulty(float areaDifficulty)
|
||||
{
|
||||
const float CurveModifier = 1.5f;
|
||||
const float DifficultyMultiplier = 1.14f;
|
||||
const float BaseDifficulty = -3f;
|
||||
return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty;
|
||||
}
|
||||
}
|
||||
|
||||
partial void GenerateLocationConnectionVisuals();
|
||||
@@ -671,7 +680,7 @@ namespace Barotrauma
|
||||
SelectedConnection.Passed = true;
|
||||
|
||||
CurrentLocation = SelectedLocation;
|
||||
CurrentLocation.Discovered = true;
|
||||
CurrentLocation.Discover();
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
@@ -702,7 +711,7 @@ namespace Barotrauma
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discovered = true;
|
||||
CurrentLocation.Discover();
|
||||
|
||||
if (prevLocation != CurrentLocation)
|
||||
{
|
||||
@@ -1055,7 +1064,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
location.Discovered = subElement.GetAttributeBool("discovered", false);
|
||||
if (subElement.GetAttributeBool("discovered", false))
|
||||
{
|
||||
location.Discover(checkTalents: false);
|
||||
}
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Barotrauma
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement(nameof(Radiation));
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//observable collection because some entities may need to be notified when the collection is modified
|
||||
public readonly ObservableCollection<MapEntity> linkedTo = new ObservableCollection<MapEntity>();
|
||||
public readonly List<MapEntity> linkedTo = new List<MapEntity>();
|
||||
|
||||
protected bool flippedX, flippedY;
|
||||
public bool FlippedX { get { return flippedX; } }
|
||||
@@ -225,12 +224,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public RuinGeneration.Ruin ParentRuin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool RemoveIfLinkedOutpostDoorInUse
|
||||
{
|
||||
@@ -416,14 +409,13 @@ namespace Barotrauma
|
||||
//connect clone wires to the clone items and refresh links between doors and gaps
|
||||
for (int i = 0; i < clones.Count; i++)
|
||||
{
|
||||
var cloneItem = clones[i] as Item;
|
||||
if (cloneItem == null) { continue; }
|
||||
if (!(clones[i] is Item cloneItem)) { continue; }
|
||||
|
||||
var door = cloneItem.GetComponent<Door>();
|
||||
door?.RefreshLinkedGap();
|
||||
|
||||
var cloneWire = cloneItem.GetComponent<Wire>();
|
||||
if (cloneWire == null) continue;
|
||||
if (cloneWire == null) { continue; }
|
||||
|
||||
var originalWire = ((Item)entitiesToClone[i]).GetComponent<Wire>();
|
||||
|
||||
@@ -431,10 +423,23 @@ namespace Barotrauma
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (originalWire.Connections[n] == null) { continue; }
|
||||
if (originalWire.Connections[n] == null)
|
||||
{
|
||||
var disconnectedFrom = entitiesToClone.Find(e => e is Item item && (item.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(originalWire) ?? false));
|
||||
if (disconnectedFrom == null) { continue; }
|
||||
|
||||
int disconnectedFromIndex = entitiesToClone.IndexOf(disconnectedFrom);
|
||||
var disconnectedFromClone = (clones[disconnectedFromIndex] as Item)?.GetComponent<ConnectionPanel>();
|
||||
if (disconnectedFromClone == null) { continue; }
|
||||
|
||||
disconnectedFromClone.DisconnectedWires.Add(cloneWire);
|
||||
if (cloneWire.Item.body != null) { cloneWire.Item.body.Enabled = false; }
|
||||
cloneWire.IsActive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var connectedItem = originalWire.Connections[n].Item;
|
||||
if (connectedItem == null) continue;
|
||||
if (connectedItem == null) { continue; }
|
||||
|
||||
//index of the item the wire is connected to
|
||||
int itemIndex = entitiesToClone.IndexOf(connectedItem);
|
||||
@@ -515,7 +520,11 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
if (aiTarget != null) aiTarget.Remove();
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.Remove();
|
||||
aiTarget = null;
|
||||
}
|
||||
|
||||
if (linkedTo != null)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static List<OutpostGenerationParams> Params { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
public virtual string Name { get; private set; }
|
||||
|
||||
public string Identifier { get; private set; }
|
||||
|
||||
@@ -67,6 +67,34 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
public bool LockUnusedDoors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
public bool RemoveUnusedGaps
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MinWaterPercentage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MaxWaterPercentage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", isSaveable: true), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
@@ -81,12 +109,14 @@ namespace Barotrauma
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
private OutpostGenerationParams(XElement element, string filePath)
|
||||
protected OutpostGenerationParams(XElement element, string filePath)
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
Name = element.GetAttributeString("name", Identifier);
|
||||
allowedLocationTypes = element.GetAttributeStringArray("allowedlocationtypes", Array.Empty<string>()).ToList();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
if (element == null) { return; }
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Barotrauma
|
||||
|
||||
locationType = location.GetLocationType();
|
||||
}
|
||||
|
||||
|
||||
//load the infos of the outpost module files
|
||||
List<SubmarineInfo> outpostModules = new List<SubmarineInfo>();
|
||||
foreach (ContentFile outpostModuleFile in outpostModuleFiles)
|
||||
@@ -85,6 +85,19 @@ namespace Barotrauma
|
||||
var subInfo = new SubmarineInfo(outpostModuleFile.Path);
|
||||
if (subInfo.OutpostModuleInfo != null)
|
||||
{
|
||||
if (generationParams is RuinGeneration.RuinGenerationParams)
|
||||
{
|
||||
//if the module doesn't have the ruin flag or any other flag used in the generation params, don't use it in ruins
|
||||
if (!subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") &&
|
||||
!generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Key)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
outpostModules.Add(subInfo);
|
||||
}
|
||||
}
|
||||
@@ -162,7 +175,7 @@ namespace Barotrauma
|
||||
|
||||
selectedModules.Add(new PlacedModule(initialModule, null, OutpostModuleInfo.GapPosition.None));
|
||||
selectedModules.Last().FulfilledModuleTypes.Add(initialModuleFlag);
|
||||
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType);
|
||||
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams);
|
||||
if (pendingModuleFlags.Any(flag => !flag.Equals("none", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
remainingTries--;
|
||||
@@ -233,17 +246,23 @@ namespace Barotrauma
|
||||
var selectedModule = selectedModules[i];
|
||||
sub.Info.GameVersion = selectedModule.Info.GameVersion;
|
||||
var moduleEntities = MapEntity.LoadAll(sub, selectedModule.Info.SubmarineElement, selectedModule.Info.FilePath, idOffset);
|
||||
idOffset = moduleEntities.Max(e => e.ID);
|
||||
|
||||
MapEntity.InitializeLoadedLinks(moduleEntities);
|
||||
|
||||
foreach (MapEntity entity in moduleEntities)
|
||||
foreach (MapEntity entity in moduleEntities.ToList())
|
||||
{
|
||||
entity.OriginalModuleIndex = i;
|
||||
if (!(entity is Item item)) { continue; }
|
||||
item.GetComponent<Door>()?.RefreshLinkedGap();
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
door.RefreshLinkedGap();
|
||||
if (!moduleEntities.Contains(door.LinkedGap)) { moduleEntities.Add(door.LinkedGap); }
|
||||
}
|
||||
item.GetComponent<ConnectionPanel>()?.InitializeLinks();
|
||||
item.GetComponent<ItemContainer>()?.OnMapLoaded();
|
||||
}
|
||||
idOffset = moduleEntities.Max(e => e.ID);
|
||||
|
||||
var wallEntities = moduleEntities.Where(e => e is Structure).Cast<Structure>();
|
||||
var hullEntities = moduleEntities.Where(e => e is Hull).Cast<Hull>();
|
||||
@@ -345,11 +364,33 @@ namespace Barotrauma
|
||||
Submarine.RepositionEntities(module.Offset + sub.HiddenSubPosition, entities[module]);
|
||||
}
|
||||
Gap.UpdateHulls();
|
||||
allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities));
|
||||
allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities, generationParams is RuinGeneration.RuinGenerationParams));
|
||||
LinkOxygenGenerators(allEntities);
|
||||
LockUnusedDoors(selectedModules, entities);
|
||||
if (generationParams.LockUnusedDoors)
|
||||
{
|
||||
LockUnusedDoors(selectedModules, entities, generationParams.RemoveUnusedGaps);
|
||||
}
|
||||
AlignLadders(selectedModules, entities);
|
||||
PowerUpOutpost(entities.SelectMany(e => e.Value));
|
||||
if (generationParams.MaxWaterPercentage > 0.0f)
|
||||
{
|
||||
foreach (var entity in allEntities)
|
||||
{
|
||||
if (entity is Hull hull)
|
||||
{
|
||||
float diff = generationParams.MaxWaterPercentage - generationParams.MinWaterPercentage;
|
||||
if (diff < 0.01f)
|
||||
{
|
||||
// Overfill the hulls to get rid of air pockets in the vertical hallways. Airpockets make it impossible to swim up the hallways.
|
||||
hull.WaterVolume = hull.Volume * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
hull.WaterVolume = hull.Volume * Rand.Range(generationParams.MinWaterPercentage, generationParams.MaxWaterPercentage, Rand.RandSync.Server) * 0.01f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allEntities;
|
||||
@@ -414,7 +455,8 @@ namespace Barotrauma
|
||||
List<string> pendingModuleFlags,
|
||||
List<PlacedModule> selectedModules,
|
||||
LocationType locationType,
|
||||
bool retry = true)
|
||||
bool retry = true,
|
||||
bool allowExtendBelowInitialModule = false)
|
||||
{
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
|
||||
@@ -422,8 +464,11 @@ namespace Barotrauma
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions().Randomize(Rand.RandSync.Server))
|
||||
{
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
//don't continue downwards if it'd extend below the airlock
|
||||
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
|
||||
if (!allowExtendBelowInitialModule)
|
||||
{
|
||||
//don't continue downwards if it'd extend below the airlock
|
||||
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
|
||||
}
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
var newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
@@ -438,7 +483,7 @@ namespace Barotrauma
|
||||
//try to append to some other module first
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false))
|
||||
if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -454,7 +499,7 @@ namespace Barotrauma
|
||||
//retry
|
||||
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
if (currentModule == null) { break; }
|
||||
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false))
|
||||
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -676,6 +721,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
availableModules = modules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
if (moduleFlag != "hallwayhorizontal" && moduleFlag != "hallwayvertical")
|
||||
{
|
||||
availableModules = availableModules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayhorizontal") && !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayvertical"));
|
||||
}
|
||||
}
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
@@ -840,7 +889,7 @@ namespace Barotrauma
|
||||
return from.AllowAttachToModules.Any(s => to.ModuleFlags.Contains(s));
|
||||
}
|
||||
|
||||
private static List<MapEntity> GenerateHallways(Submarine sub, LocationType locationType, IEnumerable<PlacedModule> placedModules, IEnumerable<SubmarineInfo> availableModules, Dictionary<PlacedModule, List<MapEntity>> allEntities)
|
||||
private static List<MapEntity> GenerateHallways(Submarine sub, LocationType locationType, IEnumerable<PlacedModule> placedModules, IEnumerable<SubmarineInfo> availableModules, Dictionary<PlacedModule, List<MapEntity>> allEntities, bool isRuin)
|
||||
{
|
||||
//if a hallway is shorter than this, one of the doors at the ends of the hallway is removed
|
||||
const float MinTwoDoorHallwayLength = 32.0f;
|
||||
@@ -1193,14 +1242,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities)
|
||||
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities, bool removeUnusedGaps)
|
||||
{
|
||||
foreach (PlacedModule module in placedModules)
|
||||
{
|
||||
foreach (MapEntity me in entities[module])
|
||||
{
|
||||
var gap = me as Gap;
|
||||
if (gap == null) { continue; }
|
||||
if (!(me is Gap gap)) { continue; }
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door != null && !door.UseBetweenOutpostModules) { continue; }
|
||||
if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap))
|
||||
@@ -1247,11 +1295,11 @@ namespace Barotrauma
|
||||
if (connectionPanel != null) { connectionPanel.Locked = true; }
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (removeUnusedGaps)
|
||||
{
|
||||
gap.Remove();
|
||||
WayPoint.WayPointList.Where(wp => wp.ConnectedGap == gap).ForEachMod(wp => wp.Remove());
|
||||
}
|
||||
}
|
||||
}
|
||||
entities[module].RemoveAll(e => e.Removed);
|
||||
}
|
||||
|
||||
@@ -89,11 +89,13 @@ namespace Barotrauma
|
||||
if (newFlags.Contains("hallwayhorizontal"))
|
||||
{
|
||||
moduleFlags.Add("hallwayhorizontal");
|
||||
if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); }
|
||||
return;
|
||||
}
|
||||
if (newFlags.Contains("hallwayvertical"))
|
||||
{
|
||||
moduleFlags.Add("hallwayvertical");
|
||||
if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); }
|
||||
return;
|
||||
}
|
||||
if (!newFlags.Any())
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace Barotrauma
|
||||
/// The item isn't available in stores unless the level's difficulty is above this value
|
||||
/// </summary>
|
||||
public readonly int MinLevelDifficulty;
|
||||
/// <summary>
|
||||
/// The cost of item when sold by the store. Higher modifier means the item costs more to buy from the store.
|
||||
/// </summary>
|
||||
public readonly float BuyingPriceMultiplier = 1f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Support for the old style of determining item prices
|
||||
@@ -34,6 +39,7 @@ namespace Barotrauma
|
||||
{
|
||||
Price = element.GetAttributeInt("buyprice", 0);
|
||||
MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
CanBeBought = true;
|
||||
var minAmount = GetMinAmount(element);
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
@@ -42,11 +48,12 @@ namespace Barotrauma
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0)
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
BuyingPriceMultiplier = buyingPriceMultiplier;
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, minAmount);
|
||||
MinLevelDifficulty = minLevelDifficulty;
|
||||
@@ -62,6 +69,7 @@ namespace Barotrauma
|
||||
var maxAmount = GetMaxAmount(element);
|
||||
var minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
var canBeSpecial = element.GetAttributeBool("canbespecial", true);
|
||||
var buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
var priceInfos = new List<Tuple<string, PriceInfo>>();
|
||||
|
||||
foreach (XElement childElement in element.GetChildElements("price"))
|
||||
@@ -73,7 +81,7 @@ namespace Barotrauma
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial,
|
||||
childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty))));
|
||||
childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty), childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier))));
|
||||
}
|
||||
|
||||
var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true);
|
||||
@@ -81,7 +89,7 @@ namespace Barotrauma
|
||||
minAmount: canBeBoughtAtOtherLocations ? minAmount : 0,
|
||||
maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0,
|
||||
canBeSpecial,
|
||||
minLevelDifficulty);
|
||||
minLevelDifficulty, buyingPriceMultiplier);
|
||||
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Lights;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -47,7 +49,7 @@ namespace Barotrauma
|
||||
const float LeakThreshold = 0.1f;
|
||||
|
||||
#if CLIENT
|
||||
private SpriteEffects SpriteEffects = SpriteEffects.None;
|
||||
public SpriteEffects SpriteEffects = SpriteEffects.None;
|
||||
#endif
|
||||
|
||||
//dimensions of the wall sections' physics bodies (only used for debug rendering)
|
||||
@@ -96,7 +98,7 @@ namespace Barotrauma
|
||||
{
|
||||
get { return Prefab.Body; }
|
||||
}
|
||||
|
||||
|
||||
public List<Body> Bodies { get; private set; }
|
||||
|
||||
public bool CastShadow
|
||||
@@ -112,7 +114,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private float? maxHealth;
|
||||
|
||||
|
||||
[Serialize(100.0f, true)]
|
||||
public float MaxHealth
|
||||
{
|
||||
@@ -167,7 +169,7 @@ namespace Barotrauma
|
||||
{
|
||||
get { return prefab.Tags; }
|
||||
}
|
||||
|
||||
|
||||
protected Color spriteColor;
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", true)]
|
||||
public Color SpriteColor
|
||||
@@ -175,7 +177,7 @@ namespace Barotrauma
|
||||
get { return spriteColor; }
|
||||
set { spriteColor = value; }
|
||||
}
|
||||
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
public bool UseDropShadow
|
||||
{
|
||||
@@ -203,8 +205,8 @@ namespace Barotrauma
|
||||
|
||||
if (!ResizeHorizontal || !ResizeVertical)
|
||||
{
|
||||
int newWidth = ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale);
|
||||
int newHeight = ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale);
|
||||
int newWidth = Math.Max(ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale), 1);
|
||||
int newHeight = Math.Max(ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale), 1);
|
||||
Rect = new Rectangle(rect.X, rect.Y, newWidth, newHeight);
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
@@ -215,6 +217,13 @@ namespace Barotrauma
|
||||
UpdateSections();
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
foreach (LightSource light in Lights)
|
||||
{
|
||||
light.SpriteScale = scale * textureScale;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +239,13 @@ namespace Barotrauma
|
||||
textureScale = new Vector2(
|
||||
MathHelper.Clamp(value.X, 0.01f, 10),
|
||||
MathHelper.Clamp(value.Y, 0.01f, 10));
|
||||
|
||||
#if CLIENT
|
||||
foreach (LightSource light in Lights)
|
||||
{
|
||||
light.LightTextureScale = textureScale * scale;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +254,13 @@ namespace Barotrauma
|
||||
public Vector2 TextureOffset
|
||||
{
|
||||
get { return textureOffset; }
|
||||
set { textureOffset = value; }
|
||||
set
|
||||
{
|
||||
textureOffset = value;
|
||||
#if CLIENT
|
||||
SetLightTextureOffset();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -281,10 +303,10 @@ namespace Barotrauma
|
||||
secRect.X += value.X; secRect.Y += value.Y;
|
||||
sec.rect = secRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float BodyWidth
|
||||
{
|
||||
get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth * scale : rect.Width; }
|
||||
@@ -363,6 +385,12 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
convexHulls?.ForEach(x => x.Move(amount));
|
||||
|
||||
foreach (LightSource light in Lights)
|
||||
{
|
||||
light.LightTextureTargetSize = rect.Size.ToVector2();
|
||||
light.Position = rect.Location.ToVector2();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -374,7 +402,7 @@ namespace Barotrauma
|
||||
defaultRect = rectangle;
|
||||
|
||||
maxHealth = sp.Health;
|
||||
|
||||
|
||||
rect = rectangle;
|
||||
TextureScale = sp.TextureScale;
|
||||
|
||||
@@ -426,25 +454,60 @@ namespace Barotrauma
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
CreateStairBodies();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
|
||||
|
||||
// Only add ai targets automatically to submarine/outpost walls
|
||||
#if CLIENT
|
||||
foreach (XElement subElement in sp.ConfigElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("light", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Vector2 pos = rect.Location.ToVector2();
|
||||
pos.Y += rect.Height;
|
||||
LightSource light = new LightSource(subElement)
|
||||
{
|
||||
ParentSub = Submarine,
|
||||
Position = rect.Location.ToVector2(),
|
||||
CastShadows = false,
|
||||
IsBackground = false,
|
||||
Color = subElement.GetAttributeColor("lightcolor", Color.White),
|
||||
SpriteScale = Vector2.One,
|
||||
Range = 0,
|
||||
LightTextureTargetSize = rect.Size.ToVector2(),
|
||||
LightTextureScale = textureScale * scale,
|
||||
LightSourceParams =
|
||||
{
|
||||
Flicker = subElement.GetAttributeFloat("flicker", 0f),
|
||||
FlickerSpeed = subElement.GetAttributeFloat("flickerspeed", 0f),
|
||||
PulseAmount = subElement.GetAttributeFloat("pulseamount", 0f),
|
||||
PulseFrequency = subElement.GetAttributeFloat("pulsefrequency", 0f),
|
||||
BlinkFrequency = subElement.GetAttributeFloat("blinkfrequency", 0f)
|
||||
}
|
||||
};
|
||||
|
||||
Lights.Add(light);
|
||||
|
||||
SetLightTextureOffset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only add ai targets automatically to submarine/outpost walls
|
||||
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget)
|
||||
{
|
||||
aiTarget = new AITarget(this)
|
||||
{
|
||||
MinSightRange = 2000,
|
||||
MaxSightRange = 5000,
|
||||
MinSightRange = 1000,
|
||||
MaxSightRange = 4000,
|
||||
MaxSoundRange = 0
|
||||
};
|
||||
}
|
||||
|
||||
InsertToList();
|
||||
|
||||
|
||||
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
||||
}
|
||||
|
||||
@@ -476,7 +539,7 @@ namespace Barotrauma
|
||||
{
|
||||
Bodies = new List<Body>();
|
||||
bodyDebugDimensions.Clear();
|
||||
|
||||
|
||||
float stairAngle = MathHelper.ToRadians(Math.Min(Prefab.StairAngle, 75.0f));
|
||||
|
||||
float bodyWidth = ConvertUnits.ToSimUnits(rect.Width / Math.Cos(stairAngle));
|
||||
@@ -504,9 +567,9 @@ namespace Barotrauma
|
||||
{
|
||||
int xsections = 1, ysections = 1;
|
||||
int width = rect.Width, height = rect.Height;
|
||||
|
||||
|
||||
if (!HasBody)
|
||||
{
|
||||
{
|
||||
if (FlippedX && IsHorizontal)
|
||||
{
|
||||
xsections = (int)Math.Ceiling((float)rect.Width / prefab.sprite.SourceRect.Width);
|
||||
@@ -548,8 +611,8 @@ namespace Barotrauma
|
||||
if (FlippedX || FlippedY)
|
||||
{
|
||||
Rectangle sectionRect = new Rectangle(
|
||||
FlippedX ? rect.Right - (x + 1) * width : rect.X + x * width,
|
||||
FlippedY ? rect.Y - rect.Height + (y + 1) * height : rect.Y - y * height,
|
||||
FlippedX ? rect.Right - (x + 1) * width : rect.X + x * width,
|
||||
FlippedY ? rect.Y - rect.Height + (y + 1) * height : rect.Y - y * height,
|
||||
width, height);
|
||||
|
||||
if (FlippedX)
|
||||
@@ -645,8 +708,8 @@ namespace Barotrauma
|
||||
|
||||
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, BodyRotation);
|
||||
|
||||
return
|
||||
Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
|
||||
return
|
||||
Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
|
||||
Math.Abs(transformedMousePos.Y - bodyPos.Y) < rectSize.Y / 2.0f;
|
||||
}
|
||||
else
|
||||
@@ -692,6 +755,10 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
|
||||
foreach (LightSource light in Lights)
|
||||
{
|
||||
light.Remove();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -724,6 +791,10 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
|
||||
foreach (LightSource light in Lights)
|
||||
{
|
||||
light.Remove();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -781,7 +852,7 @@ namespace Barotrauma
|
||||
|
||||
return (IsHorizontal ? Sections[sectionIndex].rect.Width : Sections[sectionIndex].rect.Height);
|
||||
}
|
||||
|
||||
|
||||
public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
|
||||
{
|
||||
if (!upgrade.Prefab.IsWallUpgrade) { return false; }
|
||||
@@ -799,7 +870,7 @@ namespace Barotrauma
|
||||
Upgrades.Add(upgrade);
|
||||
upgrade.ApplyUpgrade();
|
||||
}
|
||||
|
||||
|
||||
UpdateSections();
|
||||
|
||||
return true;
|
||||
@@ -914,7 +985,7 @@ namespace Barotrauma
|
||||
{
|
||||
diffFromCenter = -diffFromCenter;
|
||||
}
|
||||
|
||||
|
||||
Vector2 sectionPos = Position + new Vector2(
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
|
||||
@@ -925,7 +996,7 @@ namespace Barotrauma
|
||||
}
|
||||
return sectionPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
|
||||
{
|
||||
@@ -948,13 +1019,28 @@ namespace Barotrauma
|
||||
GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (playSound && damageAmount > 0)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags);
|
||||
string damageSound = Prefab.DamageSound;
|
||||
if (string.IsNullOrWhiteSpace(damageSound))
|
||||
{
|
||||
damageSound = attack.StructureSoundType;
|
||||
}
|
||||
SoundPlayer.PlayDamageSound(damageSound, damageAmount, worldPosition, tags: Tags);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Submarine != null && damageAmount > 0 && attacker != null)
|
||||
{
|
||||
var abilityAttackerSubmarine = new AbilityCharacterSubmarine(attacker, Submarine);
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, abilityAttackerSubmarine);
|
||||
}
|
||||
}
|
||||
|
||||
return new AttackResult(damageAmount, null);
|
||||
}
|
||||
|
||||
@@ -965,7 +1051,7 @@ namespace Barotrauma
|
||||
if (!MathUtils.IsValid(damage)) { return; }
|
||||
|
||||
damage = MathHelper.Clamp(damage, 0.0f, MaxHealth - Prefab.MinHealth);
|
||||
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && createNetworkEvent && damage != Sections[sectionIndex].damage)
|
||||
{
|
||||
@@ -1011,10 +1097,10 @@ namespace Barotrauma
|
||||
{
|
||||
diffFromCenter = (gapRect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth;
|
||||
if (BodyWidth > 0.0f) { gapRect.Width = (int)(BodyWidth * (gapRect.Width / (float)this.rect.Width)); }
|
||||
if (BodyHeight > 0.0f)
|
||||
{
|
||||
if (BodyHeight > 0.0f)
|
||||
{
|
||||
gapRect.Y = (gapRect.Y - gapRect.Height / 2) + (int)(BodyHeight / 2 + BodyOffset.Y * scale);
|
||||
gapRect.Height = (int)BodyHeight;
|
||||
gapRect.Height = (int)BodyHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1023,7 +1109,7 @@ namespace Barotrauma
|
||||
if (BodyWidth > 0.0f)
|
||||
{
|
||||
gapRect.X = gapRect.Center.X + (int)(-BodyWidth / 2 + BodyOffset.X * scale);
|
||||
gapRect.Width = (int)BodyWidth;
|
||||
gapRect.Width = (int)BodyWidth;
|
||||
}
|
||||
if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); }
|
||||
}
|
||||
@@ -1042,7 +1128,7 @@ namespace Barotrauma
|
||||
gapRect.Y += 10;
|
||||
gapRect.Width += 20;
|
||||
gapRect.Height += 20;
|
||||
|
||||
|
||||
bool horizontalGap = !IsHorizontal;
|
||||
if (Prefab.BodyRotation != 0.0f)
|
||||
{
|
||||
@@ -1079,7 +1165,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float gapOpen = MaxHealth <= 0.0f ? 0.0f : (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
}
|
||||
|
||||
float damageDiff = damage - Sections[sectionIndex].damage;
|
||||
@@ -1095,9 +1181,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (damageDiff < 0.0f)
|
||||
{
|
||||
attacker.Info?.IncreaseSkillLevel("mechanical",
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f),
|
||||
SectionPosition(sectionIndex));
|
||||
attacker.Info?.IncreaseSkillLevel("mechanical",
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1105,7 +1190,7 @@ namespace Barotrauma
|
||||
bool hasHole = SectionBodyDisabled(sectionIndex);
|
||||
|
||||
if (hadHole == hasHole) { return; }
|
||||
|
||||
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
@@ -1187,7 +1272,7 @@ namespace Barotrauma
|
||||
if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1);
|
||||
}
|
||||
if (FlippedX) { diffFromCenter = -diffFromCenter; }
|
||||
|
||||
|
||||
Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale;
|
||||
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
|
||||
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
|
||||
@@ -1229,7 +1314,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation);
|
||||
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
base.FlipX(relativeToSub);
|
||||
@@ -1250,7 +1335,7 @@ namespace Barotrauma
|
||||
|
||||
CreateStairBodies();
|
||||
}
|
||||
|
||||
|
||||
if (HasBody)
|
||||
{
|
||||
CreateSections();
|
||||
@@ -1377,7 +1462,7 @@ namespace Barotrauma
|
||||
StructurePrefab prefab = null;
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
//legacy support:
|
||||
//legacy support:
|
||||
//1. attempt to find a prefab with an empty identifier and a matching name
|
||||
prefab = MapEntityPrefab.Find(name, "") as StructurePrefab;
|
||||
//2. not found, attempt to find a prefab with a matching name
|
||||
@@ -1422,7 +1507,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
|
||||
|
||||
foreach (var upgrade in Upgrades)
|
||||
{
|
||||
upgrade.Save(element);
|
||||
@@ -1453,7 +1538,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (aiTarget != null)
|
||||
{
|
||||
aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange;
|
||||
aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : MathHelper.Lerp(aiTarget.MinSightRange, aiTarget.MaxSightRange, Submarine.Velocity.Length() / 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,9 @@ namespace Barotrauma
|
||||
private set { size = value; }
|
||||
}
|
||||
|
||||
[Serialize("", true)]
|
||||
public string DamageSound { get; private set; }
|
||||
|
||||
public Vector2 ScaledSize => size * Scale;
|
||||
|
||||
protected Vector2 textureScale = Vector2.One;
|
||||
@@ -291,7 +294,8 @@ namespace Barotrauma
|
||||
{
|
||||
case "sprite":
|
||||
sp.sprite = new Sprite(subElement, lazyLoad: true);
|
||||
if (subElement.Attribute("sourcerect") == null)
|
||||
if (subElement.Attribute("sourcerect") == null &&
|
||||
subElement.Attribute("sheetindex") == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + sp.name + "\"!");
|
||||
}
|
||||
|
||||
@@ -249,6 +249,17 @@ namespace Barotrauma
|
||||
get { return subBody?.HullVertices; }
|
||||
}
|
||||
|
||||
private int? submarineSpecificIDTag;
|
||||
public int SubmarineSpecificIDTag
|
||||
{
|
||||
get
|
||||
{
|
||||
submarineSpecificIDTag ??= ToolBox.StringToInt((Level.Loaded?.Seed ?? "") + Info.Name);
|
||||
return submarineSpecificIDTag.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool AtDamageDepth
|
||||
{
|
||||
get
|
||||
@@ -329,48 +340,6 @@ namespace Barotrauma
|
||||
DockedTo.ForEach(s => s.ShowSonarMarker = false);
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.None;
|
||||
|
||||
string defaultTag = Level.Loaded.GetWreckIDTag("wreck_id", this);
|
||||
ReplaceIDCardTagRequirements("wreck_id", defaultTag);
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != this) { continue; }
|
||||
if (item.prefab.Identifier == "idcardwreck" || item.prefab.Identifier == "idcard")
|
||||
{
|
||||
foreach (string tag in item.GetTags().ToList())
|
||||
{
|
||||
if (tag == "smallitem") { continue; }
|
||||
string newTag = Level.Loaded.GetWreckIDTag(tag, this);
|
||||
item.ReplaceTag(tag, newTag);
|
||||
ReplaceIDCardTagRequirements(tag, newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceIDCardTagRequirements(string oldTag, string newTag)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != this) { continue; }
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ReplaceIDCardTagRequirement(ic, RelatedItem.RelationType.Picked, oldTag, newTag);
|
||||
ReplaceIDCardTagRequirement(ic, RelatedItem.RelationType.Equipped, oldTag, newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ReplaceIDCardTagRequirement(ItemComponent ic, RelatedItem.RelationType relationType, string oldTag, string newTag)
|
||||
{
|
||||
if (!ic.requiredItems.ContainsKey(relationType)) { return; }
|
||||
foreach (RelatedItem requiredItem in ic.requiredItems[relationType])
|
||||
{
|
||||
int index = Array.IndexOf(requiredItem.Identifiers, oldTag);
|
||||
if (index == -1) { continue; }
|
||||
requiredItem.Identifiers[index] = newTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WreckAI WreckAI { get; private set; }
|
||||
@@ -945,9 +914,11 @@ namespace Barotrauma
|
||||
mapEntity.Move(-HiddenSubPosition);
|
||||
}
|
||||
|
||||
var prevBodyType = subBody.Body.BodyType;
|
||||
Vector2 pos = new Vector2(subBody.Position.X, subBody.Position.Y);
|
||||
subBody.Body.Remove();
|
||||
subBody = new SubmarineBody(this);
|
||||
subBody.Body.BodyType = prevBodyType;
|
||||
SetPosition(pos, new List<Submarine>(parents.Where(p => p != this)));
|
||||
|
||||
if (entityGrid != null)
|
||||
@@ -1460,6 +1431,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (info.IsRuin)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
}
|
||||
|
||||
if (entityGrid != null)
|
||||
@@ -1718,7 +1694,10 @@ namespace Barotrauma
|
||||
|
||||
PhysicsBody.RemoveAll();
|
||||
|
||||
GameMain.World.Clear();
|
||||
GameMain.World?.Clear();
|
||||
GameMain.World = null;
|
||||
|
||||
GC.Collect();
|
||||
|
||||
Unloading = false;
|
||||
}
|
||||
@@ -1730,6 +1709,9 @@ namespace Barotrauma
|
||||
subBody?.Remove();
|
||||
subBody = null;
|
||||
|
||||
outdoorNodes?.Clear();
|
||||
outdoorNodes = null;
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
|
||||
{
|
||||
GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged -= ResetCrushDepth;
|
||||
@@ -1743,8 +1725,8 @@ namespace Barotrauma
|
||||
|
||||
visibleEntities = null;
|
||||
|
||||
if (MainSub == this) MainSub = null;
|
||||
if (MainSubs[1] == this) MainSubs[1] = null;
|
||||
if (MainSub == this) { MainSub = null; }
|
||||
if (MainSubs[1] == this) { MainSubs[1] = null; }
|
||||
|
||||
ConnectedDockingPorts?.Clear();
|
||||
|
||||
|
||||
@@ -452,7 +452,7 @@ namespace Barotrauma
|
||||
|
||||
public void ApplyForce(Vector2 force)
|
||||
{
|
||||
Body.ApplyForce(force, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
Body.ApplyForce(force);
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
@@ -569,8 +569,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
|
||||
targetPos = character.WorldPosition;
|
||||
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 500.0f);
|
||||
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
|
||||
if (adjacentGap == null) { return true; }
|
||||
|
||||
if (newHull != null)
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma
|
||||
HideInMenus = 2
|
||||
}
|
||||
|
||||
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine }
|
||||
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine, Ruin }
|
||||
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
|
||||
|
||||
partial class SubmarineInfo : IDisposable
|
||||
@@ -96,9 +96,11 @@ namespace Barotrauma
|
||||
public OutpostModuleInfo OutpostModuleInfo { get; set; }
|
||||
|
||||
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
|
||||
|
||||
public bool IsWreck => Type == SubmarineType.Wreck;
|
||||
public bool IsBeacon => Type == SubmarineType.BeaconStation;
|
||||
public bool IsPlayer => Type == SubmarineType.Player;
|
||||
public bool IsRuin => Type == SubmarineType.Ruin;
|
||||
|
||||
public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined;
|
||||
public bool IsCampaignCompatibleIgnoreClass => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus);
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.RuinGeneration;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -47,6 +46,7 @@ namespace Barotrauma
|
||||
public Hull CurrentHull { get; private set; }
|
||||
|
||||
public Level.Tunnel Tunnel;
|
||||
public RuinGeneration.Ruin Ruin;
|
||||
|
||||
public SpawnType SpawnType
|
||||
{
|
||||
@@ -54,6 +54,8 @@ namespace Barotrauma
|
||||
set { spawnType = value; }
|
||||
}
|
||||
|
||||
public Action<WayPoint> OnLinksChanged { get; set; }
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
@@ -187,61 +189,141 @@ namespace Barotrauma
|
||||
door.Body.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool isFlooded = submarine.Info.IsRuin || submarine.Info.Type == SubmarineType.OutpostModule && submarine.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin");
|
||||
float diffFromHullEdge = 50;
|
||||
float minDist = 100.0f;
|
||||
float heightFromFloor = 110.0f;
|
||||
float hullMinHeight = 100;
|
||||
var removals = new List<WayPoint>();
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
// Ignore hulls that a human couldn't fit in.
|
||||
// Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually.
|
||||
if (hull.Rect.Height < hullMinHeight) { continue; }
|
||||
// Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
|
||||
Body floor = null;
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (isFlooded)
|
||||
{
|
||||
float horizontalOffset = 0;
|
||||
switch (i)
|
||||
diffFromHullEdge = 75;
|
||||
var hullWaypoints = new List<WayPoint>();
|
||||
float top = hull.Rect.Y;
|
||||
float bottom = hull.Rect.Y - hull.Rect.Height;
|
||||
if (hull.Rect.Width < 300 || hull.Rect.Height < 300)
|
||||
{
|
||||
case 1:
|
||||
horizontalOffset = hull.RectWidth * 0.2f;
|
||||
break;
|
||||
case 2:
|
||||
horizontalOffset = hull.RectWidth * 0.4f;
|
||||
break;
|
||||
case 3:
|
||||
horizontalOffset = -hull.RectWidth * 0.2f;
|
||||
break;
|
||||
case 4:
|
||||
horizontalOffset = -hull.RectWidth * 0.4f;
|
||||
break;
|
||||
// For narrow hulls, create one line of waypoints either horizontally or vertically
|
||||
if (hull.Rect.Width > hull.Rect.Height)
|
||||
{
|
||||
// Horizontal
|
||||
float y = hull.Rect.Y - hull.Rect.Height / 2;
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
{
|
||||
hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertical
|
||||
float x = hull.Rect.X + hull.Rect.Width / 2;
|
||||
for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist)
|
||||
{
|
||||
hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hullWaypoints.None())
|
||||
{
|
||||
// Try to create a grid-like network of waypoints
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
{
|
||||
for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist)
|
||||
{
|
||||
hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
|
||||
}
|
||||
}
|
||||
if (hullWaypoints.None())
|
||||
{
|
||||
// If that fails, just create one waypoint at the center.
|
||||
hullWaypoints.Add(new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height / 2), SpawnType.Path, submarine));
|
||||
}
|
||||
foreach (WayPoint wp in hullWaypoints)
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.HasBody)
|
||||
{
|
||||
// Remove waypoints that are too close/inside the walls.
|
||||
Rectangle rect = wall.Rect;
|
||||
rect.Inflate(10, 10);
|
||||
if (rect.ContainsWorld(wp.Position))
|
||||
{
|
||||
removals.Add(wp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Connect the waypoints
|
||||
foreach (var wayPoint in hullWaypoints)
|
||||
{
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.9f, minDist));
|
||||
if (closest != null && closest.CurrentHull == wayPoint.CurrentHull)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(minDist, minDist * 1.9f));
|
||||
if (closest != null && closest.CurrentHull == wayPoint.CurrentHull)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
}
|
||||
horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
|
||||
Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
|
||||
floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
if (floor != null) { break; }
|
||||
}
|
||||
if (floor == null) { continue; }
|
||||
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
|
||||
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
|
||||
{
|
||||
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
WayPoint prevWaypoint = null;
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
if (hull.Rect.Height < hullMinHeight) { continue; }
|
||||
// Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
|
||||
Body floor = null;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); }
|
||||
prevWaypoint = wayPoint;
|
||||
float horizontalOffset = 0;
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
horizontalOffset = hull.RectWidth * 0.2f;
|
||||
break;
|
||||
case 2:
|
||||
horizontalOffset = hull.RectWidth * 0.4f;
|
||||
break;
|
||||
case 3:
|
||||
horizontalOffset = -hull.RectWidth * 0.2f;
|
||||
break;
|
||||
case 4:
|
||||
horizontalOffset = -hull.RectWidth * 0.4f;
|
||||
break;
|
||||
}
|
||||
horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
|
||||
Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
|
||||
floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
if (floor != null) { break; }
|
||||
}
|
||||
if (prevWaypoint == null)
|
||||
if (floor == null) { continue; }
|
||||
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
|
||||
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
|
||||
{
|
||||
// Ensure that we always create at least one waypoint per hull.
|
||||
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
WayPoint previousWaypoint = null;
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
if (previousWaypoint != null) { wayPoint.ConnectTo(previousWaypoint); }
|
||||
previousWaypoint = wayPoint;
|
||||
}
|
||||
if (previousWaypoint == null)
|
||||
{
|
||||
// Ensure that we always create at least one waypoint per hull.
|
||||
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +358,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float outSideWaypointInterval = 100.0f;
|
||||
if (submarine.Info.Type != SubmarineType.OutpostModule)
|
||||
if (!isFlooded && submarine.Info.Type != SubmarineType.OutpostModule)
|
||||
{
|
||||
List<(WayPoint, int)> outsideWaypoints = new List<(WayPoint, int)>();
|
||||
|
||||
@@ -379,7 +461,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
// Remove unwanted points
|
||||
var removals = new List<WayPoint>();
|
||||
WayPoint previous = null;
|
||||
float tooClose = outSideWaypointInterval / 2;
|
||||
foreach (var wayPoint in outsideWaypoints)
|
||||
@@ -410,7 +491,6 @@ namespace Barotrauma
|
||||
foreach (WayPoint wp in removals)
|
||||
{
|
||||
outsideWaypoints.RemoveAll(w => w.Item1 == wp);
|
||||
wp.Remove();
|
||||
}
|
||||
for (int i = 0; i < outsideWaypoints.Count; i++)
|
||||
{
|
||||
@@ -431,41 +511,35 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Structure> stairList = new List<Structure>();
|
||||
foreach (MapEntity me in mapEntityList)
|
||||
{
|
||||
if (!(me is Structure stairs)) { continue; }
|
||||
|
||||
if (stairs.StairDirection != Direction.None) stairList.Add(stairs);
|
||||
}
|
||||
|
||||
foreach (Structure stairs in stairList)
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.StairDirection == Direction.None) { continue; }
|
||||
WayPoint[] stairPoints = new WayPoint[3];
|
||||
|
||||
stairPoints[0] = new WayPoint(
|
||||
new Vector2(stairs.Rect.X - 32.0f,
|
||||
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
|
||||
new Vector2(wall.Rect.X - 32.0f,
|
||||
wall.Rect.Y - (wall.StairDirection == Direction.Left ? 80 : wall.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
|
||||
|
||||
stairPoints[1] = new WayPoint(
|
||||
new Vector2(stairs.Rect.Right + 32.0f,
|
||||
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
|
||||
new Vector2(wall.Rect.Right + 32.0f,
|
||||
wall.Rect.Y - (wall.StairDirection == Direction.Left ? wall.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
|
||||
|
||||
for (int i = 0; i < 2; i++ )
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(100, 70));
|
||||
if (closest == null) { continue; }
|
||||
stairPoints[i].ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine);
|
||||
stairPoints[0].ConnectTo(stairPoints[2]);
|
||||
stairPoints[2].ConnectTo(stairPoints[1]);
|
||||
}
|
||||
removals.ForEach(wp => wp.Remove());
|
||||
removals.Clear();
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
@@ -603,12 +677,25 @@ namespace Barotrauma
|
||||
{
|
||||
if (gap.IsHorizontal)
|
||||
{
|
||||
// Too small to walk through
|
||||
if (gap.Rect.Height < hullMinHeight) { continue; }
|
||||
if ( isFlooded)
|
||||
{
|
||||
// Too small to swim through
|
||||
if (gap.Rect.Height < 50) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Too small to walk through
|
||||
if (gap.Rect.Height < hullMinHeight) { continue; }
|
||||
}
|
||||
|
||||
Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor);
|
||||
if (isFlooded)
|
||||
{
|
||||
pos.Y = gap.Rect.Y - gap.Rect.Height / 2;
|
||||
}
|
||||
var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
|
||||
// The closest waypoint can be quite far if the gap is at an exterior door.
|
||||
Vector2 tolerance = gap.IsRoomToRoom ? new Vector2(150, 70) : new Vector2(1000, 1000);
|
||||
Vector2 tolerance = gap.IsRoomToRoom && !isFlooded ? new Vector2(150, 70) : new Vector2(1000, 1000);
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, tolerance, gap.ConnectedDoor?.Body.FarseerBody);
|
||||
@@ -621,7 +708,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Create waypoints on vertical gaps on the outer walls, also hatches.
|
||||
if (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull)) { continue; }
|
||||
if (!isFlooded && (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull))) { continue; }
|
||||
// Too small to swim through
|
||||
if (gap.Rect.Width < 50.0f) { continue; }
|
||||
Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2);
|
||||
@@ -630,11 +717,20 @@ namespace Barotrauma
|
||||
var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
|
||||
Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull);
|
||||
int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y);
|
||||
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(50, 100));
|
||||
WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100));
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
if (isFlooded)
|
||||
{
|
||||
closest = wayPoint.FindClosest(-dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100));
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
// Link to outside
|
||||
for (dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), gap.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null);
|
||||
@@ -654,7 +750,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
|
||||
if (wp.SpawnType == SpawnType.Path && wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color.");
|
||||
}
|
||||
@@ -761,17 +857,24 @@ namespace Barotrauma
|
||||
public void ConnectTo(WayPoint wayPoint2)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(this != wayPoint2);
|
||||
|
||||
if (!linkedTo.Contains(wayPoint2)) { linkedTo.Add(wayPoint2); }
|
||||
if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); }
|
||||
if (!linkedTo.Contains(wayPoint2))
|
||||
{
|
||||
OnLinksChanged?.Invoke(this);
|
||||
linkedTo.Add(wayPoint2);
|
||||
}
|
||||
if (!wayPoint2.linkedTo.Contains(this))
|
||||
{
|
||||
wayPoint2.OnLinksChanged?.Invoke(wayPoint2);
|
||||
wayPoint2.linkedTo.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
|
||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null)
|
||||
{
|
||||
return WayPointList.GetRandom(wp =>
|
||||
wp.Submarine == sub &&
|
||||
wp.ParentRuin == ruin &&
|
||||
wp.spawnType == spawnType &&
|
||||
(string.IsNullOrEmpty(spawnPointTag) || wp.Tags.Any(t => t.Equals(spawnPointTag, StringComparison.OrdinalIgnoreCase))) &&
|
||||
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
|
||||
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
|
||||
}
|
||||
@@ -986,14 +1089,19 @@ namespace Barotrauma
|
||||
public override void ShallowRemove()
|
||||
{
|
||||
base.ShallowRemove();
|
||||
|
||||
WayPointList.Remove(this);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
CurrentHull = null;
|
||||
ConnectedGap = null;
|
||||
Tunnel = null;
|
||||
Ruin = null;
|
||||
Stairs = null;
|
||||
Ladders = null;
|
||||
OnLinksChanged = null;
|
||||
WayPointList.Remove(this);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user