v0.11.0.9
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BallastFloraPrefab : IPrefab, IDisposable
|
||||
{
|
||||
public string OriginalName { get; }
|
||||
public string Identifier { get; }
|
||||
public string FilePath { get; }
|
||||
public XElement Element { get; }
|
||||
|
||||
public ContentPackage ContentPackage { get; private set; }
|
||||
|
||||
public bool Disposed;
|
||||
|
||||
public static readonly PrefabCollection<BallastFloraPrefab> Prefabs = new PrefabCollection<BallastFloraPrefab>();
|
||||
|
||||
private BallastFloraPrefab(XElement element, string filePath, bool isOverride)
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
OriginalName = element.GetAttributeString("name", "");
|
||||
Element = element;
|
||||
FilePath = filePath;
|
||||
Prefabs.Add(this, isOverride);
|
||||
}
|
||||
|
||||
public static BallastFloraPrefab Find(string idenfitier)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(idenfitier) ? Prefabs.Find(prefab => prefab.Identifier == idenfitier) : null;
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable files)
|
||||
{
|
||||
DebugConsole.Log("Loading map creature prefabs: ");
|
||||
|
||||
foreach (ContentFile file in files) { LoadFromFile(file); }
|
||||
}
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
|
||||
var rootElement = doc?.Root;
|
||||
if (rootElement == null) { return; }
|
||||
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "ballastflorabehavior":
|
||||
{
|
||||
new BallastFloraPrefab(rootElement, file.Path, false) { ContentPackage = file.ContentPackage };
|
||||
break;
|
||||
}
|
||||
case "ballastflorabehaviors":
|
||||
{
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
XElement upgradeElement = element.GetChildElement("mapcreature");
|
||||
if (upgradeElement != null)
|
||||
{
|
||||
new BallastFloraPrefab(upgradeElement, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a map creature element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (element.Name.ToString().Equals("mapcreature", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
new BallastFloraPrefab(element, file.Path, false) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "override":
|
||||
{
|
||||
XElement mapCreatures = rootElement.GetChildElement("ballastflorabehaviors");
|
||||
if (mapCreatures != null)
|
||||
{
|
||||
foreach (XElement element in mapCreatures.Elements())
|
||||
{
|
||||
new BallastFloraPrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement element in rootElement.GetChildElements("ballastflorabehavior"))
|
||||
{
|
||||
new BallastFloraPrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}\n " +
|
||||
"Valid elements are: \"MapCreature\", \"MapCreatures\" and \"Override\".");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#nullable enable
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
class BallastFloraStateMachine
|
||||
{
|
||||
private readonly BallastFloraBehavior parent;
|
||||
|
||||
public BallastFloraStateMachine(BallastFloraBehavior parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
private IBallastFloraState? lastState;
|
||||
public IBallastFloraState? State;
|
||||
|
||||
public void EnterState(IBallastFloraState newState)
|
||||
{
|
||||
lastState = State;
|
||||
State?.Exit();
|
||||
newState.Enter();
|
||||
State = newState;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (State == null)
|
||||
{
|
||||
EnterState(new GrowIdleState(parent));
|
||||
return;
|
||||
}
|
||||
|
||||
State.Update(deltaTime);
|
||||
|
||||
switch (State.GetState())
|
||||
{
|
||||
case ExitState.Running:
|
||||
break;
|
||||
|
||||
case ExitState.ReturnLast when lastState != null && lastState.GetState() == ExitState.Running:
|
||||
EnterState(lastState);
|
||||
break;
|
||||
|
||||
default:
|
||||
EnterState(new GrowIdleState(parent));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
class DefendWithPumpState : IBallastFloraState
|
||||
{
|
||||
private readonly BallastFloraBranch targetBranch;
|
||||
private readonly List<Pump> allAvailablePumps = new List<Pump>();
|
||||
private readonly List<Door> allAvailableDoors = new List<Door>();
|
||||
private readonly List<Pump> targetPumps = new List<Pump>();
|
||||
private readonly List<Door> jammedDoors = new List<Door>();
|
||||
|
||||
private bool isFinished;
|
||||
private float timer = 10f;
|
||||
private bool filled;
|
||||
private bool tryDrown;
|
||||
private readonly Character attacker;
|
||||
|
||||
public DefendWithPumpState(BallastFloraBranch branch, List<Item> items, Character attacker)
|
||||
{
|
||||
targetBranch = branch;
|
||||
this.attacker = attacker;
|
||||
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.GetComponent<Pump>() is { } pump)
|
||||
{
|
||||
allAvailablePumps.Add(pump);
|
||||
}
|
||||
|
||||
if (item.GetComponent<Door>() is { } door)
|
||||
{
|
||||
allAvailableDoors.Add(door);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExitState GetState()
|
||||
{
|
||||
if (isFinished) { return ExitState.ReturnLast; }
|
||||
if (targetBranch.CurrentHull == null) { return ExitState.ReturnLast; }
|
||||
return timer < 0 ? ExitState.ReturnLast : ExitState.Running;
|
||||
}
|
||||
|
||||
public void Enter()
|
||||
{
|
||||
foreach (Pump pump in allAvailablePumps)
|
||||
{
|
||||
if (pump.Item.CurrentHull == targetBranch.CurrentHull)
|
||||
{
|
||||
targetPumps.Add(pump);
|
||||
SetPump(pump);
|
||||
pump.Hijacked = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPumps.Any() || targetPumps.All(p => !p.HasPower))
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
// lock the doors if the attacker is in the same hull as the ballast flora to try to drown them
|
||||
if (targetBranch.CurrentHull != null && attacker != null && attacker.CurrentHull == targetBranch.CurrentHull)
|
||||
{
|
||||
foreach (Door door in allAvailableDoors)
|
||||
{
|
||||
if (door.LinkedGap != null && door.LinkedGap.linkedTo.Contains(targetBranch.CurrentHull))
|
||||
{
|
||||
door.TrySetState(false, false, true);
|
||||
door.IsJammed = true;
|
||||
jammedDoors.Add(door);
|
||||
}
|
||||
}
|
||||
|
||||
tryDrown = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
foreach (Pump pump in targetPumps)
|
||||
{
|
||||
pump.Hijacked = false;
|
||||
}
|
||||
|
||||
foreach (Door door in jammedDoors)
|
||||
{
|
||||
door.IsJammed = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPump(Pump pump)
|
||||
{
|
||||
if (pump.TargetLevel != null)
|
||||
{
|
||||
pump.TargetLevel = 100f;
|
||||
}
|
||||
else
|
||||
{
|
||||
pump.FlowPercentage = 100f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (Pump pump in targetPumps)
|
||||
{
|
||||
SetPump(pump);
|
||||
}
|
||||
|
||||
if (tryDrown && !filled)
|
||||
{
|
||||
// keep the ballast filled for extra 10 seconds
|
||||
if (targetBranch.CurrentHull == null || targetBranch.CurrentHull.WaterPercentage >= 95f)
|
||||
{
|
||||
filled = true;
|
||||
timer += 10f;
|
||||
}
|
||||
}
|
||||
|
||||
timer -= deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
internal class GrowIdleState: IBallastFloraState
|
||||
{
|
||||
public readonly BallastFloraBehavior Behavior;
|
||||
private float growthTimer;
|
||||
|
||||
public GrowIdleState(BallastFloraBehavior behavior)
|
||||
{
|
||||
Behavior = behavior;
|
||||
}
|
||||
|
||||
public virtual ExitState GetState() => ExitState.Running;
|
||||
|
||||
public virtual void Enter()
|
||||
{
|
||||
foreach (BallastFloraBranch branch in Behavior.Branches.Where(b => b.CanGrowMore()))
|
||||
{
|
||||
if (TryScanTargets(branch)) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit() { }
|
||||
|
||||
private bool TryScanTargets(BallastFloraBranch branch)
|
||||
{
|
||||
if (ScanForTargets(branch) is { } newTarget)
|
||||
{
|
||||
Behavior.StateMachine.EnterState(new GrowToTargetState(Behavior, branch, newTarget));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (growthTimer > 0)
|
||||
{
|
||||
growthTimer -= Behavior.GetGrowthSpeed(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grow();
|
||||
UpdateIgnoredTargets();
|
||||
growthTimer = Behavior.GrowthWarps > 0 ? 0f : 5f;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Grow()
|
||||
{
|
||||
List<BallastFloraBranch> newTiles = GrowRandomly();
|
||||
#if DEBUG || UNSTABLE
|
||||
Behavior.debugSearchLines.Clear();
|
||||
#endif
|
||||
if (newTiles.Any(TryScanTargets)) { return; }
|
||||
}
|
||||
|
||||
public void UpdateIgnoredTargets()
|
||||
{
|
||||
Behavior.IgnoredTargets.ForEachMod(pair =>
|
||||
{
|
||||
var (item, delay) = pair;
|
||||
|
||||
if (delay <= 0)
|
||||
{
|
||||
Behavior.IgnoredTargets.Remove(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
Behavior.IgnoredTargets[item] = --delay;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<BallastFloraBranch> GrowRandomly()
|
||||
{
|
||||
List<BallastFloraBranch> newBranches = new List<BallastFloraBranch>();
|
||||
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(Behavior.Branches);
|
||||
foreach (BallastFloraBranch branch in newList)
|
||||
{
|
||||
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
|
||||
|
||||
if (Rand.Range(0, Behavior.Branches.Count(tile => tile.CanGrowMore())) != 0) { continue; }
|
||||
|
||||
TileSide side = branch.GetRandomFreeSide();
|
||||
|
||||
if (side == TileSide.None) { continue; }
|
||||
|
||||
Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> result);
|
||||
newBranches.AddRange(result);
|
||||
}
|
||||
|
||||
return newBranches;
|
||||
}
|
||||
|
||||
private Item? ScanForTargets(VineTile branch)
|
||||
{
|
||||
Hull parent = Behavior.Parent;
|
||||
Vector2 worldPos = Behavior.GetWorldPosition() + branch.Position;
|
||||
Vector2 pos = parent.Position + Behavior.Offset + branch.Position;
|
||||
|
||||
Vector2 diameter = ConvertUnits.ToSimUnits(new Vector2(branch.Rect.Width / 2f, branch.Rect.Height / 2f));
|
||||
Vector2 topLeft = ConvertUnits.ToSimUnits(pos) - diameter;
|
||||
Vector2 bottomRight = ConvertUnits.ToSimUnits(pos) + diameter;
|
||||
|
||||
int highestPriority = 0;
|
||||
Item? currentItem = null;
|
||||
|
||||
foreach (Item item in Item.ItemList.Where(it => !Behavior.ClaimedTargets.Contains(it)))
|
||||
{
|
||||
if (Behavior.IgnoredTargets.ContainsKey(item)) { continue; }
|
||||
|
||||
int priority = 0;
|
||||
foreach (BallastFloraBehavior.AITarget target in Behavior.Targets)
|
||||
{
|
||||
if (!target.Matches(item) || target.Priority <= highestPriority) { continue; }
|
||||
priority = target.Priority;
|
||||
break;
|
||||
}
|
||||
|
||||
if (priority == 0) { continue; }
|
||||
|
||||
if (item.Submarine != parent.Submarine || Vector2.Distance(worldPos, item.WorldPosition) > Behavior.Sight) { continue; }
|
||||
|
||||
Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position);
|
||||
|
||||
#if DEBUG || UNSTABLE
|
||||
Tuple<Vector2, Vector2> debugLine1 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(topLeft), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos - diameter));
|
||||
Tuple<Vector2, Vector2> debugLine2 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(bottomRight), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos + diameter));
|
||||
Behavior.debugSearchLines.Add(debugLine2);
|
||||
Behavior.debugSearchLines.Add(debugLine1);
|
||||
#endif
|
||||
|
||||
Body? body1 = Submarine.CheckVisibility(itemSimPos - diameter, topLeft);
|
||||
if (Blocks(body1, item)) { continue; }
|
||||
|
||||
Body? body2 = Submarine.CheckVisibility(itemSimPos + diameter, bottomRight);
|
||||
if (Blocks(body2, item)) { continue; }
|
||||
|
||||
highestPriority = priority;
|
||||
currentItem = item;
|
||||
}
|
||||
|
||||
if (currentItem != null)
|
||||
{
|
||||
foreach (BallastFloraBranch existingBranch in Behavior.Branches)
|
||||
{
|
||||
if (Behavior.BranchContainsTarget(existingBranch, currentItem))
|
||||
{
|
||||
Behavior.ClaimTarget(currentItem, existingBranch);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return currentItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
static bool Blocks(Body body, Item target)
|
||||
{
|
||||
if (body == null) { return false; }
|
||||
|
||||
switch (body.UserData)
|
||||
{
|
||||
case Submarine _:
|
||||
case Structure _:
|
||||
case Item it when it != target:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
class GrowToTargetState: GrowIdleState
|
||||
{
|
||||
public readonly List<BallastFloraBranch> TargetBranches = new List<BallastFloraBranch>();
|
||||
public readonly Item Target;
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public GrowToTargetState(BallastFloraBehavior behavior, BallastFloraBranch starter, Item target) : base(behavior)
|
||||
{
|
||||
Target = target;
|
||||
TargetBranches.Add(starter);
|
||||
}
|
||||
|
||||
// do nothing
|
||||
public override void Enter() { }
|
||||
|
||||
public override ExitState GetState() => isFinished ? ExitState.Terminate : ExitState.Running;
|
||||
|
||||
protected override void Grow()
|
||||
{
|
||||
if (Target == null || Target.Removed)
|
||||
{
|
||||
isFinished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
GrowTowardsTarget();
|
||||
}
|
||||
|
||||
private void GrowTowardsTarget()
|
||||
{
|
||||
bool succeeded = false;
|
||||
|
||||
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(TargetBranches);
|
||||
foreach (BallastFloraBranch branch in newList)
|
||||
{
|
||||
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
|
||||
|
||||
// Get what side gets us closest to the target
|
||||
TileSide side = GetClosestSide(branch, Target.WorldPosition);
|
||||
|
||||
if (branch.IsSideBlocked(side)) { continue; }
|
||||
|
||||
succeeded |= Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> newBranches);
|
||||
TargetBranches.AddRange(newBranches);
|
||||
|
||||
foreach (BallastFloraBranch newBranch in newBranches)
|
||||
{
|
||||
Rectangle worldRect = newBranch.Rect;
|
||||
worldRect.Location = Behavior.GetWorldPosition().ToPoint() + worldRect.Location;
|
||||
if (Behavior.BranchContainsTarget(newBranch, Target))
|
||||
{
|
||||
Behavior.ClaimTarget(Target, newBranch);
|
||||
isFinished = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!succeeded)
|
||||
{
|
||||
if (!Behavior.IgnoredTargets.ContainsKey(Target))
|
||||
{
|
||||
Behavior.IgnoredTargets.Add(Target, 1);
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private TileSide GetClosestSide(VineTile tile, Vector2 targetPos)
|
||||
{
|
||||
var (distX, distY) = tile.Position + Behavior.GetWorldPosition() - targetPos;
|
||||
int absDistX = (int) Math.Abs(distX), absDistY = (int) Math.Abs(distY);
|
||||
|
||||
return absDistX > absDistY ? distX > 0 ? TileSide.Left : TileSide.Right : distY > 0 ? TileSide.Bottom : TileSide.Top;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
enum ExitState
|
||||
{
|
||||
Running, // State is running
|
||||
Terminate, // State has exited
|
||||
ReturnLast // Return to the last running state if any
|
||||
}
|
||||
|
||||
interface IBallastFloraState
|
||||
{
|
||||
public void Enter();
|
||||
public void Exit();
|
||||
public void Update(float deltaTime);
|
||||
public ExitState GetState();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ namespace Barotrauma
|
||||
{
|
||||
public const ushort NullEntityID = 0;
|
||||
public const ushort EntitySpawnerID = ushort.MaxValue;
|
||||
public const ushort RespawnManagerID = ushort.MaxValue - 1;
|
||||
|
||||
public const ushort ReservedIDStart = ushort.MaxValue - 2;
|
||||
|
||||
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
public static IEnumerable<Entity> GetEntities()
|
||||
@@ -22,8 +25,6 @@ namespace Barotrauma
|
||||
|
||||
public static EntitySpawner Spawner;
|
||||
|
||||
private ushort id;
|
||||
|
||||
protected AITarget aiTarget;
|
||||
|
||||
private bool idFreed;
|
||||
@@ -39,53 +40,7 @@ namespace Barotrauma
|
||||
get { return idFreed; }
|
||||
}
|
||||
|
||||
public ushort ID
|
||||
{
|
||||
get
|
||||
{
|
||||
return id;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this is EntitySpawner) { return; }
|
||||
if (value == NullEntityID)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot set the ID of an entity to " + NullEntityID +
|
||||
"! The value is reserved for entity events referring to a non-existent (e.g. removed) entity.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
if (value == EntitySpawnerID)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot set the ID of an entity to " + EntitySpawnerID +
|
||||
"! The value is reserved for EntitySpawner.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
|
||||
if (dictionary.TryGetValue(id, out Entity thisEntity) && thisEntity == this)
|
||||
{
|
||||
dictionary.Remove(id);
|
||||
}
|
||||
//if there's already an entity with the same ID, give it the old ID of this one
|
||||
if (dictionary.TryGetValue(value, out Entity existingEntity))
|
||||
{
|
||||
DebugConsole.Log(existingEntity + " had the same ID as " + this + " (" + value + ")");
|
||||
dictionary.Remove(value);
|
||||
dictionary.Add(id, existingEntity);
|
||||
existingEntity.id = id;
|
||||
DebugConsole.Log("The id of " + existingEntity + " is now " + id);
|
||||
DebugConsole.Log("The id of " + this + " is now " + value);
|
||||
}
|
||||
|
||||
id = value;
|
||||
idFreed = false;
|
||||
dictionary.Add(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID the entity had after instantiation/loading. May have been taken up by another entity, causing a new ID to be assigned to this entity.
|
||||
/// </summary>
|
||||
public ushort OriginalID;
|
||||
public readonly ushort ID;
|
||||
|
||||
public virtual Vector2 SimPosition
|
||||
{
|
||||
@@ -125,17 +80,27 @@ namespace Barotrauma
|
||||
|
||||
private readonly double spawnTime;
|
||||
|
||||
public Entity(Submarine submarine)
|
||||
public Entity(Submarine submarine, ushort id)
|
||||
{
|
||||
this.Submarine = submarine;
|
||||
spawnTime = Timing.TotalTime;
|
||||
|
||||
if (id != NullEntityID && dictionary.ContainsKey(id))
|
||||
{
|
||||
throw new Exception($"ID {id} is taken by {dictionary[id].ToString()}");
|
||||
}
|
||||
|
||||
//give a unique ID
|
||||
id = OriginalID = this is EntitySpawner ?
|
||||
EntitySpawnerID :
|
||||
FindFreeID(submarine == null ? (ushort)1 : submarine.IdOffset);
|
||||
ID = DetermineID(id, submarine);
|
||||
|
||||
dictionary.Add(id, this);
|
||||
dictionary.Add(ID, this);
|
||||
}
|
||||
|
||||
protected virtual ushort DetermineID(ushort id, Submarine submarine)
|
||||
{
|
||||
return id != NullEntityID ?
|
||||
id :
|
||||
FindFreeID(submarine == null ? (ushort)1 : submarine.IdOffset);
|
||||
}
|
||||
|
||||
public static ushort FindFreeID(ushort idOffset = 0)
|
||||
@@ -153,7 +118,7 @@ namespace Barotrauma
|
||||
{
|
||||
id += 1;
|
||||
IDfound = dictionary.ContainsKey(id);
|
||||
} while (IDfound || id == NullEntityID || id == EntitySpawnerID);
|
||||
} while (IDfound || id == NullEntityID || id > ReservedIDStart);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -192,7 +157,7 @@ namespace Barotrauma
|
||||
errorMsg.AppendLine("Some entities were not removed in Entity.RemoveAll:");
|
||||
foreach (Entity e in dictionary.Values)
|
||||
{
|
||||
errorMsg.AppendLine(" - " + e.ToString() + "(ID " + e.id + ")");
|
||||
errorMsg.AppendLine(" - " + e.ToString() + "(ID " + e.ID + ")");
|
||||
}
|
||||
}
|
||||
if (Item.ItemList.Count > 0)
|
||||
@@ -200,7 +165,7 @@ namespace Barotrauma
|
||||
errorMsg.AppendLine("Some items were not removed in Entity.RemoveAll:");
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
errorMsg.AppendLine(" - " + item.Name + "(ID " + item.id + ")");
|
||||
errorMsg.AppendLine(" - " + item.Name + "(ID " + item.ID + ")");
|
||||
}
|
||||
|
||||
var items = new List<Item>(Item.ItemList);
|
||||
@@ -222,7 +187,7 @@ namespace Barotrauma
|
||||
errorMsg.AppendLine("Some characters were not removed in Entity.RemoveAll:");
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
errorMsg.AppendLine(" - " + character.Name + "(ID " + character.id + ")");
|
||||
errorMsg.AppendLine(" - " + character.Name + "(ID " + character.ID + ")");
|
||||
}
|
||||
|
||||
var characters = new List<Character>(Character.CharacterList);
|
||||
@@ -293,15 +258,15 @@ namespace Barotrauma
|
||||
|
||||
public static void DumpIds(int count, string filename)
|
||||
{
|
||||
List<Entity> entities = dictionary.Values.OrderByDescending(e => e.id).ToList();
|
||||
List<Entity> entities = dictionary.Values.OrderByDescending(e => e.ID).ToList();
|
||||
|
||||
count = Math.Min(entities.Count, count);
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
lines.Add(entities[i].id + ": " + entities[i].ToString());
|
||||
DebugConsole.ThrowError(entities[i].id + ": " + entities[i].ToString());
|
||||
lines.Add(entities[i].ID + ": " + entities[i].ToString());
|
||||
DebugConsole.ThrowError(entities[i].ID + ": " + entities[i].ToString());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
|
||||
@@ -6,6 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -30,8 +32,10 @@ namespace Barotrauma
|
||||
private readonly float decalSize;
|
||||
|
||||
public float EmpStrength { get; set; }
|
||||
|
||||
public float BallastFloraDamage { get; set; }
|
||||
|
||||
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f)
|
||||
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
|
||||
{
|
||||
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
|
||||
{
|
||||
@@ -39,6 +43,7 @@ namespace Barotrauma
|
||||
};
|
||||
this.force = force;
|
||||
this.EmpStrength = empStrength;
|
||||
BallastFloraDamage = ballastFloraStrength;
|
||||
sparks = true;
|
||||
shockwave = true;
|
||||
smoke = true;
|
||||
@@ -65,6 +70,7 @@ namespace Barotrauma
|
||||
if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
|
||||
|
||||
EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
|
||||
BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f);
|
||||
|
||||
decal = element.GetAttributeString("decal", "");
|
||||
decalSize = element.GetAttributeFloat(1.0f, "decalSize", "decalsize");
|
||||
@@ -125,7 +131,12 @@ namespace Barotrauma
|
||||
|
||||
if (attack.GetStructureDamage(1.0f) > 0.0f)
|
||||
{
|
||||
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attacker);
|
||||
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attack.GetLevelWallDamage(1.0f), attacker);
|
||||
}
|
||||
|
||||
if (BallastFloraDamage > 0.0f)
|
||||
{
|
||||
RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker);
|
||||
}
|
||||
|
||||
if (EmpStrength > 0.0f)
|
||||
@@ -167,8 +178,12 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Condition <= 0.0f) { continue; }
|
||||
if (Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.5f) { continue; }
|
||||
if (applyFireEffects && !item.FireProof)
|
||||
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 < attack.Range * 0.5f && applyFireEffects && !item.FireProof)
|
||||
{
|
||||
//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)
|
||||
@@ -177,9 +192,9 @@ namespace Barotrauma
|
||||
while (container != null)
|
||||
{
|
||||
if (container.FireProof)
|
||||
{
|
||||
fireProof = true;
|
||||
break;
|
||||
{
|
||||
fireProof = true;
|
||||
break;
|
||||
}
|
||||
container = container.Container;
|
||||
}
|
||||
@@ -190,20 +205,18 @@ namespace Barotrauma
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
|
||||
{
|
||||
float limbRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
|
||||
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
|
||||
if (dist > attack.Range)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }
|
||||
|
||||
damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition);
|
||||
item.Condition -= damageAmount * distFactor;
|
||||
}
|
||||
}
|
||||
@@ -241,7 +254,7 @@ namespace Barotrauma
|
||||
List<Affliction> modifiedAfflictions = new List<Affliction>();
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || limb.ignoreCollisions) { continue; }
|
||||
if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; }
|
||||
|
||||
float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
|
||||
|
||||
@@ -255,37 +268,7 @@ namespace Barotrauma
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
|
||||
//solid obstacles between the explosion and the limb reduce the effect of the explosion
|
||||
var obstacles = Submarine.PickBodies(limb.SimPosition, explosionPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
|
||||
foreach (var body in obstacles)
|
||||
{
|
||||
if (body.UserData is Item item)
|
||||
{
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null && !door.IsBroken) { distFactor *= 0.01f; }
|
||||
}
|
||||
else if (body.UserData is Structure structure)
|
||||
{
|
||||
int sectionIndex = structure.FindSectionIndex(worldPosition, world: true, clamp: true);
|
||||
if (structure.SectionBodyDisabled(sectionIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (structure.SectionIsLeaking(sectionIndex))
|
||||
{
|
||||
distFactor *= 0.1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
distFactor *= 0.01f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
distFactor *= 0.1f;
|
||||
}
|
||||
}
|
||||
if (distFactor <= 0.05f) { continue; }
|
||||
|
||||
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
|
||||
distFactors.Add(limb, distFactor);
|
||||
|
||||
modifiedAfflictions.Clear();
|
||||
@@ -372,7 +355,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||
/// </summary>
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null)
|
||||
{
|
||||
List<Structure> structureList = new List<Structure>();
|
||||
float dist = 600.0f;
|
||||
@@ -395,23 +378,123 @@ namespace Barotrauma
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) continue;
|
||||
|
||||
|
||||
structure.AddDamage(i, damage * distFactor, attacker);
|
||||
|
||||
if (damagedStructures.ContainsKey(structure))
|
||||
{
|
||||
{
|
||||
damagedStructures[structure] += damage * distFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
damagedStructures.Add(structure, damage * distFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
|
||||
{
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
|
||||
foreach (var cell in destructibleWall.Cells)
|
||||
{
|
||||
if (cell.IsPointInside(worldPosition))
|
||||
{
|
||||
destructibleWall.AddDamage(levelWallDamage, worldPosition);
|
||||
continue;
|
||||
}
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (!MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float wallDist = Vector2.DistanceSquared(worldPosition, intersection);
|
||||
if (wallDist < worldRange * worldRange)
|
||||
{
|
||||
destructibleWall.AddDamage(damage, worldPosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return damagedStructures;
|
||||
}
|
||||
|
||||
public void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
|
||||
{
|
||||
List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.BallastFlora != null) { ballastFlorae.Add(hull.BallastFlora); }
|
||||
}
|
||||
|
||||
foreach (BallastFloraBehavior ballastFlora in ballastFlorae)
|
||||
{
|
||||
float resistanceMuliplier = ballastFlora.HasBrokenThrough ? 1f : 1f - ballastFlora.ExplosionResistance;
|
||||
ballastFlora.Branches.ForEachMod(branch =>
|
||||
{
|
||||
Vector2 branchWorldPos = ballastFlora.GetWorldPosition() + branch.Position;
|
||||
float branchDist = Vector2.Distance(branchWorldPos, worldPosition);
|
||||
if (branchDist < worldRange)
|
||||
{
|
||||
float distFactor = 1.0f - (branchDist / worldRange);
|
||||
if (distFactor <= 0.0f) { return; }
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
Vector2 branchPos = branchWorldPos;
|
||||
if (ballastFlora.Parent?.Submarine != null)
|
||||
{
|
||||
explosionPos -= ballastFlora.Parent.Submarine.Position;
|
||||
branchPos -= ballastFlora.Parent.Submarine.Position;
|
||||
}
|
||||
distFactor *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, ConvertUnits.ToSimUnits(branchPos));
|
||||
ballastFlora.DamageBranch(branch, damage * distFactor * resistanceMuliplier, BallastFloraBehavior.AttackType.Explosives, attacker);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos)
|
||||
{
|
||||
float damageMultiplier = 1.0f;
|
||||
var obstacles = Submarine.PickBodies(targetSimPos, explosionSimPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
|
||||
foreach (var body in obstacles)
|
||||
{
|
||||
if (body.UserData is Item item)
|
||||
{
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null && !door.IsBroken) { damageMultiplier *= 0.01f; }
|
||||
}
|
||||
else if (body.UserData is Structure structure)
|
||||
{
|
||||
int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world: true, clamp: true);
|
||||
if (structure.SectionBodyDisabled(sectionIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (structure.SectionIsLeaking(sectionIndex))
|
||||
{
|
||||
damageMultiplier *= 0.1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageMultiplier *= 0.01f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
damageMultiplier *= 0.1f;
|
||||
}
|
||||
}
|
||||
return damageMultiplier;
|
||||
}
|
||||
|
||||
static partial void PlayTinnitusProjSpecific(float volume);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
float dmg = (float)Math.Sqrt(Math.Min(500, size.X)) * deltaTime / c.AnimController.Limbs.Count(l => !l.IsSevered);
|
||||
float dmg = (float)Math.Sqrt(Math.Min(500, size.X)) * deltaTime / c.AnimController.Limbs.Count(l => !l.IsSevered && !l.Hidden);
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
|
||||
@@ -129,8 +129,8 @@ namespace Barotrauma
|
||||
: this(rect, rect.Width < rect.Height, submarine)
|
||||
{ }
|
||||
|
||||
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine)
|
||||
: base(MapEntityPrefab.Find(null, "gap"), submarine)
|
||||
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base(MapEntityPrefab.Find(null, "gap"), submarine, id)
|
||||
{
|
||||
this.rect = rect;
|
||||
flowForce = Vector2.Zero;
|
||||
@@ -141,7 +141,8 @@ namespace Barotrauma
|
||||
GapList.Add(this);
|
||||
InsertToList();
|
||||
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * 2.0f, Vector2.UnitX * 2.0f);
|
||||
float blockerSize = ConvertUnits.ToSimUnits(Math.Max(rect.Width, rect.Height)) / 2;
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize);
|
||||
outsideCollisionBlocker.UserData = $"CollisionBlocker (Gap {ID})";
|
||||
outsideCollisionBlocker.BodyType = BodyType.Static;
|
||||
outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall;
|
||||
@@ -711,7 +712,7 @@ namespace Barotrauma
|
||||
if (!DisableHullRechecks) FindHulls();
|
||||
}
|
||||
|
||||
public static Gap Load(XElement element, Submarine submarine)
|
||||
public static Gap Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect = Rectangle.Empty;
|
||||
|
||||
@@ -737,12 +738,10 @@ namespace Barotrauma
|
||||
isHorizontal = horizontalAttribute.Value.ToString() == "true";
|
||||
}
|
||||
|
||||
Gap g = new Gap(rect, isHorizontal, submarine)
|
||||
Gap g = new Gap(rect, isHorizontal, submarine, idRemap.GetOffsetId(element))
|
||||
{
|
||||
ID = (ushort)int.Parse(element.Attribute("ID").Value),
|
||||
linkedToID = new List<ushort>(),
|
||||
};
|
||||
g.OriginalID = g.ID;
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -391,14 +392,16 @@ namespace Barotrauma
|
||||
|
||||
public List<DummyFireSource> FakeFireSources { get; private set; }
|
||||
|
||||
public BallastFloraBehavior BallastFlora { get; set; }
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (prefab, rectangle, Submarine.MainSub)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine)
|
||||
: base (prefab, submarine)
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base (prefab, submarine, id)
|
||||
{
|
||||
rect = rectangle;
|
||||
|
||||
@@ -524,6 +527,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
|
||||
|
||||
BallastFlora?.OnMapLoaded();
|
||||
}
|
||||
|
||||
public void AddToGrid(Submarine submarine)
|
||||
@@ -607,6 +612,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Remove();
|
||||
hullList.Remove(this);
|
||||
BallastFlora?.Remove();
|
||||
|
||||
if (Submarine != null && !Submarine.Loading && !Submarine.Unloading)
|
||||
{
|
||||
@@ -687,6 +693,9 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
BallastFlora?.Update(deltaTime);
|
||||
|
||||
UpdateProjSpecific(deltaTime, cam);
|
||||
|
||||
Oxygen -= OxygenDeteriorationSpeed * deltaTime;
|
||||
@@ -1352,7 +1361,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static Hull Load(XElement element, Submarine submarine)
|
||||
public static Hull Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect;
|
||||
if (element.Attribute("rect") != null)
|
||||
@@ -1369,23 +1378,13 @@ namespace Barotrauma
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
}
|
||||
|
||||
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine)
|
||||
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine, idRemap.GetOffsetId(element))
|
||||
{
|
||||
WaterVolume = element.GetAttributeFloat("pressure", 0.0f),
|
||||
ID = (ushort)int.Parse(element.Attribute("ID").Value)
|
||||
WaterVolume = element.GetAttributeFloat("pressure", 0.0f)
|
||||
};
|
||||
hull.OriginalID = hull.ID;
|
||||
hull.linkedToID = new List<ushort>();
|
||||
|
||||
string linkedToString = element.GetAttributeString("linked", "");
|
||||
if (linkedToString != "")
|
||||
{
|
||||
string[] linkedToIds = linkedToString.Split(',');
|
||||
for (int i = 0; i < linkedToIds.Length; i++)
|
||||
{
|
||||
hull.linkedToID.Add((ushort)int.Parse(linkedToIds[i]));
|
||||
}
|
||||
}
|
||||
hull.ParseLinks(element, idRemap);
|
||||
|
||||
string originalAmbientLight = element.GetAttributeString("originalambientlight", null);
|
||||
if (!string.IsNullOrWhiteSpace(originalAmbientLight))
|
||||
@@ -1410,6 +1409,15 @@ namespace Barotrauma
|
||||
decal.BaseAlpha = baseAlpha;
|
||||
}
|
||||
break;
|
||||
case "ballastflorabehavior":
|
||||
string identifier = subElement.GetAttributeString("identifier", string.Empty);
|
||||
BallastFloraPrefab prefab = BallastFloraPrefab.Find(identifier);
|
||||
if (prefab != null)
|
||||
{
|
||||
hull.BallastFlora = new BallastFloraBehavior(hull, prefab, Vector2.Zero);
|
||||
hull.BallastFlora.LoadSave(subElement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,6 +1496,8 @@ namespace Barotrauma
|
||||
));
|
||||
}
|
||||
|
||||
BallastFlora?.Save(element);
|
||||
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
parentElement.Add(element);
|
||||
return element;
|
||||
|
||||
@@ -8,5 +8,6 @@ namespace Barotrauma
|
||||
Vector2 WorldPosition { get; }
|
||||
Vector2 SimPosition { get; }
|
||||
Submarine Submarine { get; }
|
||||
bool IgnoreByAI => false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,14 +114,16 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (Screen.Selected is SubEditorScreen)
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(loaded, false));
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(loaded, false, handleInventoryBehavior: false));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectPrefabs = false)
|
||||
{
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, FilePath);
|
||||
int idOffset = Entity.FindFreeID(1);
|
||||
if (MapEntity.mapEntityList.Any()) { idOffset = MapEntity.mapEntityList.Max(e => e.ID); }
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, FilePath, idOffset);
|
||||
if (entities.Count == 0) { return entities; }
|
||||
|
||||
Vector2 offset = sub == null ? Vector2.Zero : sub.HiddenSubPosition;
|
||||
@@ -132,7 +134,16 @@ namespace Barotrauma
|
||||
me.Submarine = sub;
|
||||
if (!(me is Item item)) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire != null) { wire.MoveNodes(position - offset); }
|
||||
//Vector2 subPosition = Submarine == null ? Vector2.Zero : Submarine.HiddenSubPosition;
|
||||
if (wire != null)
|
||||
{
|
||||
//fix wires that have been erroneously saved at the "hidden position"
|
||||
if (sub != null && Vector2.Distance(me.Position, sub.HiddenSubPosition) > sub.HiddenSubPosition.Length() / 2)
|
||||
{
|
||||
me.Move(position);
|
||||
}
|
||||
wire.MoveNodes(position - offset);
|
||||
}
|
||||
}
|
||||
|
||||
MapEntity.MapLoaded(entities, true);
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CaveGenerationParams : ISerializableEntity
|
||||
{
|
||||
public static List<CaveGenerationParams> CaveParams { get; private set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
private int minWidth, maxWidth;
|
||||
private int minHeight, maxHeight;
|
||||
|
||||
private int minBranchCount, maxBranchCount;
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the commonness of the object in a specific level type.
|
||||
/// Key = name of the level type, value = commonness in that level type.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, float> OverrideCommonness = new Dictionary<string, float>();
|
||||
|
||||
[Editable, Serialize(1.0f, true)]
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(8000, true), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = Math.Max(value, 1000); }
|
||||
}
|
||||
|
||||
[Serialize(10000, true), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = Math.Max(value, minWidth); }
|
||||
}
|
||||
|
||||
[Serialize(8000, true), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
public int MinHeight
|
||||
{
|
||||
get { return minHeight; }
|
||||
set { minHeight = Math.Max(value, 1000); }
|
||||
}
|
||||
|
||||
[Serialize(10000, true), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
public int MaxHeight
|
||||
{
|
||||
get { return maxHeight; }
|
||||
set { maxHeight = Math.Max(value, minHeight); }
|
||||
}
|
||||
|
||||
[Serialize(2, true), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MinBranchCount
|
||||
{
|
||||
get { return minBranchCount; }
|
||||
set { minBranchCount = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(4, true), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MaxBranchCount
|
||||
{
|
||||
get { return maxBranchCount; }
|
||||
set { maxBranchCount = Math.Max(value, minBranchCount); }
|
||||
}
|
||||
|
||||
[Serialize(50, true), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.1f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1.0f, DecimalCount = 2 )]
|
||||
public float DestructibleWallRatio
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, Rand.RandSync rand)
|
||||
{
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams) <= 0.0f))
|
||||
{
|
||||
return CaveParams.First();
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams)).ToList(), rand);
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
private CaveGenerationParams(XElement element)
|
||||
{
|
||||
Identifier = element == null ? "default" : element.GetAttributeString("identifier", null) ?? element.Name.ToString();
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "wall":
|
||||
WallSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "walledge":
|
||||
WallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
{
|
||||
CaveParams = new List<CaveGenerationParams>();
|
||||
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.CaveGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
files = new List<ContentFile>() { new ContentFile("Content/Map/CaveGenerationParameters.xml", ContentType.CaveGenerationParameters) };
|
||||
}
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
CaveParams.Clear();
|
||||
DebugConsole.NewMessage($"Overriding cave generation parameters with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
if (isOverride)
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", "");
|
||||
var existingParams = CaveParams.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingParams != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the cave generation parameters '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
CaveParams.Remove(existingParams);
|
||||
}
|
||||
CaveParams.Add(new CaveGenerationParams(element.FirstElement()));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", "");
|
||||
var existingParams = CaveParams.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingParams != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate cave generation parameters: '{identifier}' defined in {element.Name} of '{file.Path}'. Use <override></override> tags to override the generation parameters.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
CaveParams.Add(new CaveGenerationParams(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision.Shapes;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -80,7 +81,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (MathUtils.NearlyEqual(ge.Point2.X, borders.X) || MathUtils.NearlyEqual(ge.Point2.X, borders.Right) ||
|
||||
MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom))
|
||||
MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom))
|
||||
{
|
||||
if (point1 == null)
|
||||
{
|
||||
@@ -95,14 +96,43 @@ namespace Barotrauma
|
||||
if (point1.HasValue && point2.HasValue)
|
||||
{
|
||||
Debug.Assert(point1 != point2);
|
||||
var newEdge = new GraphEdge(point1.Value, point2.Value)
|
||||
bool point1OnSide = MathUtils.NearlyEqual(point1.Value.X, borders.X) || MathUtils.NearlyEqual(point1.Value.X, borders.Right);
|
||||
bool point2OnSide = MathUtils.NearlyEqual(point2.Value.X, borders.X) || MathUtils.NearlyEqual(point2.Value.X, borders.Right);
|
||||
//one point is one the side, another on top/bottom
|
||||
// -> the cell is in the corner of the level, we need 2 edges
|
||||
if (point1OnSide != point2OnSide)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
};
|
||||
cell.Edges.Add(newEdge);
|
||||
Vector2 cornerPos = new Vector2(
|
||||
point1.Value.X < borders.Center.X ? borders.X : borders.Right,
|
||||
point1.Value.Y < borders.Center.Y ? borders.Y : borders.Bottom);
|
||||
cell.Edges.Add(
|
||||
new GraphEdge(point1.Value, cornerPos)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
});
|
||||
cell.Edges.Add(
|
||||
new GraphEdge(point2.Value, cornerPos)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.Edges.Add(
|
||||
new GraphEdge(point1.Value, point2.Value)
|
||||
{
|
||||
Cell1 = cell,
|
||||
IsSolid = true,
|
||||
Site1 = cell.Site,
|
||||
OutsideLevel = true
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -111,44 +141,16 @@ namespace Barotrauma
|
||||
return cells;
|
||||
}
|
||||
|
||||
|
||||
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
|
||||
{
|
||||
if (cell == null) { cell = edge.AdjacentCell(null); }
|
||||
if (cell == null) { return Vector2.UnitX; }
|
||||
|
||||
CompareCCW compare = new CompareCCW(cell.Center);
|
||||
if (compare.Compare(edge.Point1, edge.Point2) == -1)
|
||||
{
|
||||
var temp = edge.Point1;
|
||||
edge.Point1 = edge.Point2;
|
||||
edge.Point2 = temp;
|
||||
}
|
||||
|
||||
Vector2 normal = Vector2.Normalize(edge.Point2 - edge.Point1);
|
||||
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.Point2);
|
||||
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
if (Vector2.Dot(normal, diffToCell) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<Point> pathNodes, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
|
||||
public static void GeneratePath(Level.Tunnel tunnel, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, Rectangle limits)
|
||||
{
|
||||
var targetCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < pathNodes.Count; i++)
|
||||
for (int i = 0; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
//a search depth of 2 is large enough to find a cell in almost all maps, but in case it fails, we increase the depth
|
||||
int searchDepth = 2;
|
||||
while (searchDepth < 5)
|
||||
{
|
||||
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, searchDepth);
|
||||
int cellIndex = FindCellIndex(tunnel.Nodes[i], cells, cellGrid, gridCellSize, searchDepth);
|
||||
if (cellIndex > -1)
|
||||
{
|
||||
targetCells.Add(cells[cellIndex]);
|
||||
@@ -157,25 +159,15 @@ namespace Barotrauma
|
||||
|
||||
searchDepth++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return GeneratePath(targetCells, cells, cellGrid, gridCellSize, limits, wanderAmount, mirror);
|
||||
tunnel.Cells.AddRange(GeneratePath(targetCells, cells, limits));
|
||||
}
|
||||
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<VoronoiCell> targetCells, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells, Rectangle limits)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
//how heavily the path "steers" towards the endpoint
|
||||
//lower values will cause the path to "wander" more, higher will make it head straight to the end
|
||||
wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f);
|
||||
|
||||
List<GraphEdge> allowedEdges = new List<GraphEdge>();
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell currentCell = targetCells[0];
|
||||
@@ -190,41 +182,24 @@ namespace Barotrauma
|
||||
{
|
||||
int edgeIndex = 0;
|
||||
|
||||
allowedEdges.Clear();
|
||||
foreach (GraphEdge edge in currentCell.Edges)
|
||||
double smallestDist = double.PositiveInfinity;
|
||||
for (int i = 0; i < currentCell.Edges.Count; i++)
|
||||
{
|
||||
var adjacentCell = edge.AdjacentCell(currentCell);
|
||||
if (adjacentCell != null && limits.Contains(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y))
|
||||
var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell);
|
||||
if (adjacentCell == null) { continue; }
|
||||
double dist = MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
|
||||
dist += MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, currentCell.Site.Coord.X, currentCell.Site.Coord.Y) * 0.5f;
|
||||
//disfavor small edges to prevent generating a very small passage
|
||||
if (Vector2.Distance(currentCell.Edges[i].Point1, currentCell.Edges[i].Point2) < 200.0f)
|
||||
{
|
||||
allowedEdges.Add(edge);
|
||||
dist += 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
//steer towards target
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > wanderAmount || allowedEdges.Count == 0)
|
||||
{
|
||||
double smallestDist = double.PositiveInfinity;
|
||||
for (int i = 0; i < currentCell.Edges.Count; i++)
|
||||
if (dist < smallestDist)
|
||||
{
|
||||
var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell);
|
||||
if (adjacentCell == null) { continue; }
|
||||
double dist = MathUtils.Distance(
|
||||
adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y,
|
||||
targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
|
||||
if (dist < smallestDist)
|
||||
{
|
||||
edgeIndex = i;
|
||||
smallestDist = dist;
|
||||
}
|
||||
edgeIndex = i;
|
||||
smallestDist = dist;
|
||||
}
|
||||
}
|
||||
//choose random edge (ignoring ones where the adjacent cell is outside limits)
|
||||
else
|
||||
{
|
||||
edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server);
|
||||
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
|
||||
edgeIndex = currentCell.Edges.IndexOf(allowedEdges[edgeIndex]);
|
||||
}
|
||||
|
||||
currentCell = currentCell.Edges[edgeIndex].AdjacentCell(currentCell);
|
||||
currentCell.CellType = CellType.Path;
|
||||
@@ -234,8 +209,8 @@ namespace Barotrauma
|
||||
|
||||
if (currentCell == targetCells[currentTargetIndex])
|
||||
{
|
||||
currentTargetIndex += 1;
|
||||
if (currentTargetIndex >= targetCells.Count) break;
|
||||
currentTargetIndex++;
|
||||
if (currentTargetIndex >= targetCells.Count) { break; }
|
||||
}
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
|
||||
@@ -256,17 +231,42 @@ namespace Barotrauma
|
||||
List<GraphEdge> tempEdges = new List<GraphEdge>();
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid)
|
||||
if (!edge.IsSolid || edge.OutsideLevel)
|
||||
{
|
||||
tempEdges.Add(edge);
|
||||
continue;
|
||||
}
|
||||
|
||||
//If the edge is next to an empty cell and there's another solid cell at the other side of the empty one,
|
||||
//don't touch this edge. Otherwise we may end up closing off small passages between cells.
|
||||
var adjacentEmptyCell = edge.AdjacentCell(cell);
|
||||
if (adjacentEmptyCell?.CellType == CellType.Solid) { adjacentEmptyCell = null; }
|
||||
if (adjacentEmptyCell != null)
|
||||
{
|
||||
GraphEdge adjacentEdge = null;
|
||||
//find the edge at the opposite side of the adjacent cell
|
||||
foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges)
|
||||
{
|
||||
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) < 0 &&
|
||||
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType == CellType.Solid)
|
||||
{
|
||||
adjacentEdge = otherEdge;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (adjacentEdge != null)
|
||||
{
|
||||
tempEdges.Add(edge);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
List<Vector2> edgePoints = new List<Vector2>();
|
||||
Vector2 edgeNormal = GetEdgeNormal(edge, cell);
|
||||
Vector2 edgeNormal = edge.GetNormal(cell);
|
||||
|
||||
float edgeLength = Vector2.Distance(edge.Point1, edge.Point2);
|
||||
int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
|
||||
Vector2 edgeDir = (edge.Point2 - edge.Point1);
|
||||
Vector2 edgeDir = edge.Point2 - edge.Point1;
|
||||
for (int i = 0; i <= pointCount; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
@@ -279,16 +279,48 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
float centerF = 0.5f - Math.Abs(0.5f - (i / (float)pointCount));
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.Server);
|
||||
edgePoints.Add(
|
||||
Vector2 extrudedPoint =
|
||||
edge.Point1 +
|
||||
edgeDir * (i / (float)pointCount) -
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF);
|
||||
edgeDir * (i / (float)pointCount) +
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF;
|
||||
|
||||
var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2);
|
||||
bool isInside = false;
|
||||
foreach (var nearbyCell in nearbyCells)
|
||||
{
|
||||
if (nearbyCell == cell || nearbyCell.CellType != CellType.Solid) { continue; }
|
||||
//check if extruding the edge causes it to go inside another one
|
||||
if (nearbyCell.IsPointInside(extrudedPoint))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
}
|
||||
//check if another edge will be inside this cell after the extrusion
|
||||
Vector2 triangleCenter = (edge.Point1 + edge.Point2 + extrudedPoint) / 3;
|
||||
foreach (GraphEdge nearbyEdge in nearbyCell.Edges)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isInside) { break; }
|
||||
}
|
||||
|
||||
if (!isInside)
|
||||
{
|
||||
edgePoints.Add(extrudedPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
for (int i = 0; i < edgePoints.Count - 1; i++)
|
||||
{
|
||||
tempEdges.Add(new GraphEdge(edgePoints[i], edgePoints[i + 1])
|
||||
{
|
||||
@@ -297,7 +329,10 @@ namespace Barotrauma
|
||||
IsSolid = edge.IsSolid,
|
||||
Site1 = edge.Site1,
|
||||
Site2 = edge.Site2,
|
||||
OutsideLevel = edge.OutsideLevel
|
||||
OutsideLevel = edge.OutsideLevel,
|
||||
NextToCave = edge.NextToCave,
|
||||
NextToMainPath = edge.NextToMainPath,
|
||||
NextToSidePath = edge.NextToSidePath
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -347,15 +382,27 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, cell.Center));
|
||||
|
||||
if (bodyPoints.Count < 2) continue;
|
||||
Vector2 minVert = tempVertices[0];
|
||||
Vector2 maxVert = tempVertices[0];
|
||||
foreach (var vert in tempVertices)
|
||||
{
|
||||
minVert = new Vector2(
|
||||
Math.Min(minVert.X, vert.X),
|
||||
Math.Min(minVert.Y, vert.Y));
|
||||
maxVert = new Vector2(
|
||||
Math.Max(maxVert.X, vert.X),
|
||||
Math.Max(maxVert.Y, vert.Y));
|
||||
}
|
||||
Vector2 center = (minVert + maxVert) / 2;
|
||||
renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, center));
|
||||
|
||||
if (bodyPoints.Count < 2) { continue; }
|
||||
|
||||
if (bodyPoints.Count < 3)
|
||||
{
|
||||
foreach (Vector2 vertex in tempVertices)
|
||||
{
|
||||
if (bodyPoints.Contains(vertex)) continue;
|
||||
if (bodyPoints.Contains(vertex)) { continue; }
|
||||
bodyPoints.Add(vertex);
|
||||
break;
|
||||
}
|
||||
@@ -366,11 +413,11 @@ namespace Barotrauma
|
||||
cell.BodyVertices.Add(bodyPoints[i]);
|
||||
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
|
||||
}
|
||||
|
||||
if (cell.CellType == CellType.Empty) continue;
|
||||
|
||||
if (cell.CellType == CellType.Empty) { continue; }
|
||||
|
||||
cellBody.UserData = cell;
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center));
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(center));
|
||||
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
@@ -380,13 +427,17 @@ namespace Barotrauma
|
||||
Vector2 b = triangles[i][1];
|
||||
Vector2 c = triangles[i][2];
|
||||
float area = Math.Abs(a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y)) / 2.0f;
|
||||
if (area < 1.0f) continue;
|
||||
if (area < 1.0f) { continue; }
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
var newFixture = cellBody.CreatePolygon(bodyVertices, 5.0f);
|
||||
newFixture.UserData = cell;
|
||||
PolygonShape polygon = new PolygonShape(bodyVertices, 5.0f);
|
||||
Fixture fixture = new Fixture(polygon)
|
||||
{
|
||||
UserData = cell
|
||||
};
|
||||
cellBody.Add(fixture, resetMassData: false);
|
||||
|
||||
if (newFixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon)
|
||||
if (fixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")");
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
@@ -394,11 +445,13 @@ namespace Barotrauma
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning,
|
||||
"Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
cell.Body = cellBody;
|
||||
}
|
||||
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
cellBody.ResetMassData();
|
||||
|
||||
return cellBody;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class DestructibleLevelWall : LevelWall, IDamageable
|
||||
{
|
||||
public bool NetworkUpdatePending;
|
||||
|
||||
public float Damage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float MaxHealth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 1000.0f;
|
||||
|
||||
public bool Destroyed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float FadeOutDuration
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float FadeOutTimer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 SimPosition
|
||||
{
|
||||
get { return Body.Position; }
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(Body.Position); }
|
||||
}
|
||||
|
||||
public float Health
|
||||
{
|
||||
get { return MaxHealth - Damage; }
|
||||
}
|
||||
|
||||
public DestructibleLevelWall(List<Vector2> vertices, Color color, Level level, float? health = null, bool giftWrap = false)
|
||||
: base (vertices, color, level, giftWrap)
|
||||
{
|
||||
MaxHealth = health ?? MathHelper.Clamp(Body.Mass, 100.0f, 1000.0f);
|
||||
Cells.ForEach(c => c.IsDestructible = true);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (FadeOutDuration > 0.0f)
|
||||
{
|
||||
FadeOutTimer += deltaTime;
|
||||
if (FadeOutTimer > FadeOutDuration && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsClient)) { Destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDamage(float damage, Vector2 worldPosition)
|
||||
{
|
||||
AddDamageProjSpecific(damage, worldPosition);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (Destroyed) { return; }
|
||||
if (!MathUtils.NearlyEqual(damage, 0.0f)) { NetworkUpdatePending = true; }
|
||||
Damage += damage;
|
||||
if (Damage >= MaxHealth)
|
||||
{
|
||||
CreateFragments();
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(float damage, Vector2 worldPosition);
|
||||
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
{
|
||||
AddDamage(attack.StructureDamage, worldPosition);
|
||||
return new AttackResult(attack.StructureDamage);
|
||||
}
|
||||
|
||||
private void CreateFragments()
|
||||
{
|
||||
#if CLIENT
|
||||
SoundPlayer.PlaySound("icebreak", WorldPosition);
|
||||
#endif
|
||||
//generate initial triangles (one triangle from each edge to the center of the cell)
|
||||
List<List<Vector2>> triangles = new List<List<Vector2>>();
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
List<Vector2> triangleVerts = new List<Vector2>
|
||||
{
|
||||
edge.Point1 + cell.Translation,
|
||||
edge.Point2 + cell.Translation,
|
||||
cell.Center
|
||||
};
|
||||
triangles.Add(triangleVerts);
|
||||
}
|
||||
}
|
||||
|
||||
//split triangles that have edges more than 1000 units long
|
||||
Pair<int, int> longestEdge = new Pair<int, int>(-1, -1);
|
||||
float longestEdgeLength = 0.0f;
|
||||
do
|
||||
{
|
||||
longestEdge.First = -1;
|
||||
longestEdge.Second = -1;
|
||||
longestEdgeLength = 0.0f;
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
for (int edge = 0; edge < 3; edge++)
|
||||
{
|
||||
float edgeLength = Vector2.Distance(triangles[i][edge], triangles[i][(edge + 1) % 3]);
|
||||
if (edgeLength > longestEdgeLength)
|
||||
{
|
||||
longestEdge.First = i;
|
||||
longestEdge.Second = edge;
|
||||
longestEdgeLength = edgeLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (longestEdgeLength < 1000.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Vector2 p0 = triangles[longestEdge.First][longestEdge.Second];
|
||||
Vector2 p1 = triangles[longestEdge.First][(longestEdge.Second + 1) % 3];
|
||||
Vector2 p2 = triangles[longestEdge.First][(longestEdge.Second + 2) % 3];
|
||||
triangles[longestEdge.First] = new List<Vector2> { p0, (p0 + p1) / 2, p2 };
|
||||
triangles.Add(new List<Vector2> { (p0 + p1) / 2, p1, p2 });
|
||||
|
||||
|
||||
} while (triangles.Count < 32);
|
||||
|
||||
//generate fragments
|
||||
foreach (var triangle in triangles)
|
||||
{
|
||||
Vector2 triangleCenter = (triangle[0] + triangle[1]+ triangle[2]) / 3;
|
||||
triangle[0] -= triangleCenter;
|
||||
triangle[1] -= triangleCenter;
|
||||
triangle[2] -= triangleCenter;
|
||||
Vector2 simTriangleCenter = ConvertUnits.ToSimUnits(triangleCenter);
|
||||
|
||||
DestructibleLevelWall fragment = new DestructibleLevelWall(triangle, Color.White, Level.Loaded, giftWrap: true);
|
||||
fragment.Damage = fragment.MaxHealth;
|
||||
fragment.Body.Position = simTriangleCenter;
|
||||
fragment.Body.BodyType = BodyType.Dynamic;
|
||||
fragment.Body.FixedRotation = false;
|
||||
fragment.Body.LinearDamping = Rand.Range(0.2f, 0.3f);
|
||||
fragment.Body.AngularDamping = Rand.Range(0.1f, 0.2f);
|
||||
fragment.Body.GravityScale = 0.1f;
|
||||
fragment.Body.Mass *= 10.0f;
|
||||
fragment.Body.CollisionCategories = Physics.CollisionNone;
|
||||
fragment.Body.CollidesWith = Physics.CollisionWall;
|
||||
fragment.FadeOutDuration = 20.0f;
|
||||
|
||||
Vector2 bodyDiff = simTriangleCenter - Body.Position;
|
||||
fragment.Body.LinearVelocity = (bodyDiff + Rand.Vector(0.5f)).ClampLength(15.0f);
|
||||
fragment.Body.AngularVelocity = Rand.Range(-0.5f, 0.5f);// MathHelper.Clamp(-bodyDiff.X * 0.1f, -0.5f, 0.5f);
|
||||
|
||||
Level.Loaded.UnsyncedExtraWalls.Add(fragment);
|
||||
|
||||
#if CLIENT
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
int startEdgeIndex = Rand.Int(3);
|
||||
Vector2 pos1 = triangle[startEdgeIndex];
|
||||
Vector2 pos2 = triangle[(startEdgeIndex + 1) % 3];
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("iceshards",
|
||||
triangleCenter + Vector2.Lerp(pos1, pos2, Rand.Range(0.0f, 1.0f)),
|
||||
Rand.Vector(Rand.Range(50.0f, 1000.0f)) + fragment.Body.LinearVelocity * 100.0f);
|
||||
if (particle != null)
|
||||
{
|
||||
particle.Size *= Rand.Range(1.0f, 5.0f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (Destroyed) { return; }
|
||||
Destroyed = true;
|
||||
level?.UnsyncedExtraWalls?.Remove(this);
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
}
|
||||
GameMain.World.Remove(Body);
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,13 +26,33 @@ namespace Barotrauma
|
||||
|
||||
public readonly LevelGenerationParams GenerationParams;
|
||||
|
||||
public bool HasBeaconStation;
|
||||
public bool IsBeaconActive;
|
||||
|
||||
public OutpostGenerationParams ForceOutpostGenerationParams;
|
||||
|
||||
public readonly Point Size;
|
||||
|
||||
public readonly int InitialDepth;
|
||||
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
|
||||
public float CrushDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
return Math.Max(Size.Y, Level.DefaultRealWorldCrushDepth / Physics.DisplayToRealWorldRatio) - InitialDepth;
|
||||
}
|
||||
}
|
||||
public float RealWorldCrushDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
return Math.Max(Size.Y * Physics.DisplayToRealWorldRatio, Level.DefaultRealWorldCrushDepth);
|
||||
}
|
||||
}
|
||||
|
||||
public LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
|
||||
{
|
||||
Seed = seed ?? throw new ArgumentException("Seed was null");
|
||||
@@ -44,6 +64,8 @@ namespace Barotrauma
|
||||
sizeFactor = MathHelper.Clamp(sizeFactor, 0.0f, 1.0f);
|
||||
int width = (int)MathHelper.Lerp(generationParams.MinWidth, generationParams.MaxWidth, sizeFactor);
|
||||
|
||||
InitialDepth = (int)MathHelper.Lerp(generationParams.InitialDepthMin, generationParams.InitialDepthMax, sizeFactor);
|
||||
|
||||
Size = new Point(
|
||||
(int)MathUtils.Round(width, Level.GridCellSize),
|
||||
(int)MathUtils.Round(generationParams.Height, Level.GridCellSize));
|
||||
@@ -56,8 +78,11 @@ namespace Barotrauma
|
||||
Size = element.GetAttributePoint("size", new Point(1000));
|
||||
Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type);
|
||||
|
||||
HasBeaconStation = element.GetAttributeBool("hasbeaconstation", false);
|
||||
IsBeaconActive = element.GetAttributeBool("isbeaconactive", false);
|
||||
|
||||
string generationParamsId = element.GetAttributeString("generationparams", "");
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId);
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || l.OldIdentifier == generationParamsId);
|
||||
if (GenerationParams == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading a level. Could not find level generation params with the ID \"{generationParamsId}\".");
|
||||
@@ -68,8 +93,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
InitialDepth = element.GetAttributeInt("initialdepth", GenerationParams.InitialDepthMin);
|
||||
|
||||
string biomeIdentifier = element.GetAttributeString("biome", "");
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier);
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier || b.OldIdentifier == biomeIdentifier);
|
||||
if (Biome == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in level data: could not find the biome \"{biomeIdentifier}\".");
|
||||
@@ -104,6 +131,12 @@ namespace Barotrauma
|
||||
Size = new Point(
|
||||
(int)MathUtils.Round(width, Level.GridCellSize),
|
||||
(int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
|
||||
HasBeaconStation = rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
IsBeaconActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,6 +152,7 @@ namespace Barotrauma
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble());
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
Size = new Point(
|
||||
(int)MathUtils.Round(width, Level.GridCellSize),
|
||||
(int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
|
||||
@@ -136,16 +170,24 @@ namespace Barotrauma
|
||||
LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type;
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
var biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
|
||||
var biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
|
||||
LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server);
|
||||
|
||||
return new LevelData(
|
||||
var levelData = new LevelData(
|
||||
seed,
|
||||
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
|
||||
generationParams,
|
||||
biome);
|
||||
if (type == LevelType.LocationConnection)
|
||||
{
|
||||
float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
||||
levelData.HasBeaconStation = beaconRng < 0.5f;
|
||||
levelData.IsBeaconActive = beaconRng > 0.25f;
|
||||
}
|
||||
GameMain.GameSession?.GameMode?.Mission?.AdjustLevelData(levelData);
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
@@ -156,7 +198,15 @@ namespace Barotrauma
|
||||
new XAttribute("type", Type.ToString()),
|
||||
new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("size", XMLExtensions.PointToString(Size)),
|
||||
new XAttribute("generationparams", GenerationParams.Identifier));
|
||||
new XAttribute("generationparams", GenerationParams.Identifier),
|
||||
new XAttribute("initialdepth", InitialDepth));
|
||||
|
||||
if (HasBeaconStation)
|
||||
{
|
||||
newElement.Add(
|
||||
new XAttribute("hasbeaconstation", HasBeaconStation.ToString()),
|
||||
new XAttribute("isbeaconactive", IsBeaconActive.ToString()));
|
||||
}
|
||||
|
||||
if (Type == LevelType.Outpost)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -10,6 +9,7 @@ namespace Barotrauma
|
||||
class Biome
|
||||
{
|
||||
public readonly string Identifier;
|
||||
public readonly string OldIdentifier;
|
||||
public readonly string DisplayName;
|
||||
public readonly string Description;
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Barotrauma
|
||||
public Biome(XElement element)
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
OldIdentifier = element.GetAttributeString("oldidentifier", null);
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
Identifier = element.GetAttributeString("name", "");
|
||||
@@ -61,6 +62,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly string OldIdentifier;
|
||||
|
||||
private int minWidth, maxWidth, height;
|
||||
|
||||
private Point voronoiSiteInterval;
|
||||
@@ -72,9 +75,7 @@ namespace Barotrauma
|
||||
//x = min interval, y = max interval
|
||||
private Point mainPathNodeIntervalRange;
|
||||
|
||||
private int smallTunnelCount;
|
||||
//x = min length, y = max length
|
||||
private Point smallTunnelLengthRange;
|
||||
private int caveCount;
|
||||
|
||||
//how large portion of the bottom of the level should be "carved out"
|
||||
//if 0.0f, the bottom will be completely solid (making the abyss unreachable)
|
||||
@@ -96,6 +97,8 @@ namespace Barotrauma
|
||||
|
||||
private float waterParticleScale;
|
||||
|
||||
private int initialDepthMin, initialDepthMax;
|
||||
|
||||
//which biomes can this type of level appear in
|
||||
private readonly List<Biome> allowedBiomes = new List<Biome>();
|
||||
|
||||
@@ -146,7 +149,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 startPosition;
|
||||
[Serialize("0,0", true, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable]
|
||||
[Serialize("0,0", true, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get { return startPosition; }
|
||||
@@ -159,7 +162,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 endPosition;
|
||||
[Serialize("1,0", true, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable]
|
||||
[Serialize("1,0", true, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get { return endPosition; }
|
||||
@@ -185,25 +188,46 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(80, true, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int BackgroundCreatureAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = Math.Max(value, 2000); }
|
||||
set { minWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(100000, true), Editable]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = Math.Max(value, 2000); }
|
||||
set { maxWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(50000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
|
||||
[Serialize(50000, true), Editable]
|
||||
public int Height
|
||||
{
|
||||
get { return height; }
|
||||
set { height = Math.Max(value, 2000); }
|
||||
set { height = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMin
|
||||
{
|
||||
get { return initialDepthMin; }
|
||||
set { initialDepthMin = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMax
|
||||
{
|
||||
get { return initialDepthMax; }
|
||||
set { initialDepthMax = Math.Max(value, initialDepthMin); }
|
||||
}
|
||||
|
||||
[Serialize(6500, true), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
@@ -213,6 +237,29 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0,1", true), Editable]
|
||||
public Point SideTunnelCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SideTunnelVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2000,6000", true), Editable]
|
||||
public Point MinSideTunnelRadius
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("3000, 3000", true, description: "How far from each other voronoi sites are placed. " +
|
||||
"Sites determine shape of the voronoi graph which the level walls are generated from. " +
|
||||
"(Decreasing this value causes the number of sites, and the complexity of the level, to increase exponentially - be careful when adjusting)")]
|
||||
@@ -239,7 +286,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueInt = 100, MaxValueInt = 10000), Serialize(1000, true, description: "The edges of the individual wall cells are subdivided into edges of this size. "
|
||||
[Editable(MinValueInt = 500, MaxValueInt = 10000), Serialize(5000, true, description: "The edges of the individual wall cells are subdivided into edges of this size. "
|
||||
+ "Can be used in conjunction with the rounding values to make the cells rounder. Smaller values will make the cells look smoother, " +
|
||||
"but make the level more performance-intensive as the number of polygons used in rendering and physics calculations increases.")]
|
||||
public int CellSubdivisionLength
|
||||
@@ -287,23 +334,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(5, true, description: "The number of small tunnels placed along the main path.")]
|
||||
public int SmallTunnelCount
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float MainPathVariance
|
||||
{
|
||||
get { return smallTunnelCount; }
|
||||
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
|
||||
Serialize("5000, 10000", true, description: "The minimum and maximum length of small tunnels placed along the main path.")]
|
||||
public Point SmallTunnelLengthRange
|
||||
[Editable, Serialize(5, true, description: "The number of caves placed along the main path.")]
|
||||
public int CaveCount
|
||||
{
|
||||
get { return smallTunnelLengthRange; }
|
||||
set
|
||||
{
|
||||
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100, MinWidth);
|
||||
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, MinWidth);
|
||||
}
|
||||
get { return caveCount; }
|
||||
set { caveCount = MathHelper.Clamp(value, 0, 100); }
|
||||
}
|
||||
|
||||
[Serialize(100, true), Editable(MinValueInt = 0, MaxValueInt = 10000)]
|
||||
@@ -313,6 +355,34 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("19200,38400", true, description: "The minimum and maximum distance between two resource spawn points on a path."), Editable(100, 100000)]
|
||||
public Point ResourceIntervalRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("9600,19200", true, description: "The minimum and maximum distance between two resource spawn points on a cave path."), Editable(100, 100000)]
|
||||
public Point CaveResourceIntervalRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2,8", true, description: "The minimum and maximum amount of resources in a single cluster. " +
|
||||
"In addition to this, resource commonness affects the cluster size. Less common resources spawn in smaller clusters."), Editable(1, 20)]
|
||||
public Point ResourceClusterSizeRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.3f, true, description: "How likely a resource spawn point on a path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float ResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "How likely a resource spawn point on a cave path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float CaveResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int FloatingIceChunkCount
|
||||
{
|
||||
@@ -320,6 +390,20 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 100)]
|
||||
public int IslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int IceSpireCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
public int SeaFloorDepth
|
||||
{
|
||||
@@ -415,12 +499,41 @@ namespace Barotrauma
|
||||
private set { waterParticleScale = Math.Max(value, 0.01f); }
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, true, description: "Size of the level wall texture."), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallTextureSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, true), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeTextureWidth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, true, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
public float WallEdgeExpandOutwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, true, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeExpandInwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite BackgroundSprite { get; private set; }
|
||||
public Sprite BackgroundTopSprite { get; private set; }
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallSpriteSpecular { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
public Sprite WallEdgeSpriteSpecular { get; private set; }
|
||||
public Sprite DestructibleWallSprite { get; private set; }
|
||||
public Sprite DestructibleWallEdgeSprite { get; private set; }
|
||||
public Sprite WallSpriteDestroyed { get; private set; }
|
||||
public Sprite WaterParticles { get; private set; }
|
||||
|
||||
public static IEnumerable<Biome> GetBiomes()
|
||||
@@ -469,6 +582,8 @@ namespace Barotrauma
|
||||
{
|
||||
Identifier = element == null ? "default" :
|
||||
element.GetAttributeString("identifier", null) ?? element.Name.ToString();
|
||||
OldIdentifier = element?.GetAttributeString("oldidentifier", null)?.ToLowerInvariant();
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
if (element == null) { return; }
|
||||
@@ -486,7 +601,8 @@ namespace Barotrauma
|
||||
string biomeName = biomeNames[i].Trim().ToLowerInvariant();
|
||||
if (biomeName == "none") { continue; }
|
||||
|
||||
Biome matchingBiome = biomes.Find(b => b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
|
||||
Biome matchingBiome = biomes.Find(b =>
|
||||
b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase) || (b.OldIdentifier?.Equals(biomeName, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||
if (matchingBiome == null)
|
||||
{
|
||||
matchingBiome = biomes.Find(b => b.DisplayName.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -518,14 +634,17 @@ namespace Barotrauma
|
||||
case "wall":
|
||||
WallSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "wallspecular":
|
||||
WallSpriteSpecular = new Sprite(subElement);
|
||||
break;
|
||||
case "walledge":
|
||||
WallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "walledgespecular":
|
||||
WallEdgeSpriteSpecular = new Sprite(subElement);
|
||||
case "destructiblewall":
|
||||
DestructibleWallSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "destructiblewalledge":
|
||||
DestructibleWallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "walldestroyed":
|
||||
WallSpriteDestroyed = new Sprite(subElement);
|
||||
break;
|
||||
case "waterparticles":
|
||||
WaterParticles = new Sprite(subElement);
|
||||
@@ -546,8 +665,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<XElement> biomeElements = new List<XElement>();
|
||||
List<XElement> levelParamElements = new List<XElement>();
|
||||
|
||||
Dictionary<string, XElement> levelParamElements = new Dictionary<string, XElement>();
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
@@ -557,18 +675,18 @@ namespace Barotrauma
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
biomeElements.Clear();
|
||||
levelParamElements.Clear();
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters and biomes with '{file.Path}'", Color.Yellow);
|
||||
DebugConsole.NewMessage($"Overriding biomes with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
else if (biomeElements.Any() || levelParamElements.Any())
|
||||
else if (biomeElements.Any() && mainElement.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Another level generation parameter file already loaded! Use <override></override> tags to override it.");
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Another level generation parameter file already loaded! Use <override></override> tags to override the biomes.");
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
bool isOverride = element.IsOverride();
|
||||
if (isOverride)
|
||||
{
|
||||
if (element.FirstElement().Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -578,18 +696,31 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Clear();
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters with '{file.Path}'", Color.Yellow);
|
||||
levelParamElements.AddRange(element.Elements());
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", null) ?? element.GetAttributeString("name", "");
|
||||
if (levelParamElements.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
levelParamElements.Remove(identifier);
|
||||
}
|
||||
levelParamElements.Add(identifier, element.FirstElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
biomeElements.AddRange(element.Elements());
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Add(element);
|
||||
string identifier = element.GetAttributeString("identifier", null) ?? element.GetAttributeString("name", "");
|
||||
if (levelParamElements.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate level generation parameters: '{identifier}' defined in {element.Name} of '{file.Path}'. Use <override></override> tags to override the generation parameters.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Add(identifier, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -599,7 +730,7 @@ namespace Barotrauma
|
||||
biomes.Add(new Biome(biomeElement));
|
||||
}
|
||||
|
||||
foreach (XElement levelParamElement in levelParamElements)
|
||||
foreach (XElement levelParamElement in levelParamElements.Values)
|
||||
{
|
||||
LevelParams.Add(new LevelGenerationParams(levelParamElement));
|
||||
}
|
||||
|
||||
@@ -37,18 +37,23 @@ namespace Barotrauma
|
||||
|
||||
public bool NeedsNetworkSyncing
|
||||
{
|
||||
get { return Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); }
|
||||
get { return Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
set
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedsUpdate
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return spriteIndex < 0 || Prefab.Sprites.Count == 0 ? null : Prefab.Sprites[spriteIndex % Prefab.Sprites.Count]; }
|
||||
}
|
||||
public Sprite SpecularSprite
|
||||
{
|
||||
get { return spriteIndex < 0 || Prefab.SpecularSprites.Count == 0 ? null : Prefab.SpecularSprites[spriteIndex % Prefab.SpecularSprites.Count]; }
|
||||
}
|
||||
|
||||
Vector2 ISpatialEntity.Position => new Vector2(Position.X, Position.Y);
|
||||
|
||||
@@ -60,8 +65,6 @@ namespace Barotrauma
|
||||
|
||||
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
{
|
||||
Triggers = new List<LevelTrigger>();
|
||||
|
||||
ActivePrefab = Prefab = prefab;
|
||||
Position = position;
|
||||
Scale = scale;
|
||||
@@ -69,13 +72,26 @@ namespace Barotrauma
|
||||
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
|
||||
|
||||
if (prefab.PhysicsBodyElement != null)
|
||||
if (Sprite != null && prefab.SpriteSpecificPhysicsBodyElements.ContainsKey(Sprite))
|
||||
{
|
||||
PhysicsBody = new PhysicsBody(prefab.SpriteSpecificPhysicsBodyElements[Sprite], ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
|
||||
}
|
||||
else if (prefab.PhysicsBodyElement != null)
|
||||
{
|
||||
PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
|
||||
}
|
||||
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition, -Rotation);
|
||||
PhysicsBody.BodyType = BodyType.Static;
|
||||
PhysicsBody.CollisionCategories = Physics.CollisionLevel;
|
||||
PhysicsBody.CollidesWith = Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
foreach (XElement triggerElement in prefab.LevelTriggerElements)
|
||||
{
|
||||
Triggers ??= new List<LevelTrigger>();
|
||||
Vector2 triggerPosition = triggerElement.GetAttributeVector2("position", Vector2.Zero) * scale;
|
||||
|
||||
if (rotation != 0.0f)
|
||||
@@ -90,10 +106,12 @@ namespace Barotrauma
|
||||
|
||||
var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name);
|
||||
int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent);
|
||||
if (parentTriggerIndex > -1) newTrigger.ParentTrigger = Triggers[parentTriggerIndex];
|
||||
if (parentTriggerIndex > -1) { newTrigger.ParentTrigger = Triggers[parentTriggerIndex]; }
|
||||
Triggers.Add(newTrigger);
|
||||
}
|
||||
|
||||
NeedsUpdate = NeedsNetworkSyncing || (Triggers != null && Triggers.Any()) || Prefab.PhysicsBodyTriggerIndex > -1;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -131,9 +149,10 @@ namespace Barotrauma
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
for (int j = 0; j < Triggers.Count; j++)
|
||||
{
|
||||
if (!Triggers[j].UseNetworkSyncing) continue;
|
||||
if (!Triggers[j].UseNetworkSyncing) { continue; }
|
||||
Triggers[j].ServerWrite(msg, c);
|
||||
}
|
||||
}
|
||||
|
||||
+191
-93
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -17,9 +18,10 @@ namespace Barotrauma
|
||||
const int GridSize = 2000;
|
||||
|
||||
private List<LevelObject> objects;
|
||||
private List<LevelObject> updateableObjects;
|
||||
private List<LevelObject>[,] objectGrid;
|
||||
|
||||
public LevelObjectManager() : base(null)
|
||||
public LevelObjectManager() : base(null, Entity.NullEntityID)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,20 +29,36 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly GraphEdge GraphEdge;
|
||||
public readonly Vector2 Normal;
|
||||
public readonly LevelObjectPrefab.SpawnPosType SpawnPosType;
|
||||
public readonly List<LevelObjectPrefab.SpawnPosType> SpawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>();
|
||||
public readonly Alignment Alignment;
|
||||
public readonly float Length;
|
||||
|
||||
private readonly float noiseVal;
|
||||
|
||||
|
||||
public SpawnPosition(GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment)
|
||||
: this(graphEdge, normal, spawnPosType.ToEnumerable(), alignment)
|
||||
{ }
|
||||
|
||||
public SpawnPosition(GraphEdge graphEdge, Vector2 normal, IEnumerable<LevelObjectPrefab.SpawnPosType> spawnPosTypes, Alignment alignment)
|
||||
{
|
||||
GraphEdge = graphEdge;
|
||||
Normal = normal;
|
||||
SpawnPosType = spawnPosType;
|
||||
Alignment = alignment;
|
||||
|
||||
Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
|
||||
Normal = normal.NearlyEquals(Vector2.Zero) ? Vector2.UnitY : Vector2.Normalize(normal);
|
||||
SpawnPosTypes.AddRange(spawnPosTypes);
|
||||
|
||||
if (spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.MainPath) ||
|
||||
spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelStart) ||
|
||||
spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelEnd))
|
||||
{
|
||||
Length = 1000.0f;
|
||||
Normal = Vector2.Zero;
|
||||
Alignment = Alignment.Any;
|
||||
}
|
||||
else
|
||||
{
|
||||
Alignment = alignment;
|
||||
Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
|
||||
}
|
||||
|
||||
noiseVal =
|
||||
(float)(PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, 0.5f) +
|
||||
@@ -83,7 +101,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var posOfInterest in level.PositionsOfInterest)
|
||||
{
|
||||
if (posOfInterest.PositionType != Level.PositionType.MainPath) continue;
|
||||
if (posOfInterest.PositionType != Level.PositionType.MainPath && posOfInterest.PositionType != Level.PositionType.SidePath) { continue; }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(posOfInterest.Position.ToVector2(), posOfInterest.Position.ToVector2() + Vector2.UnitX),
|
||||
@@ -101,49 +119,29 @@ namespace Barotrauma
|
||||
|
||||
var availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List);
|
||||
objects = new List<LevelObject>();
|
||||
updateableObjects = new List<LevelObject>();
|
||||
|
||||
Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
|
||||
Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams.Identifier, availablePrefabs);
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams, availablePrefabs);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
suitableSpawnPositions.Add(prefab, availableSpawnPositions.Where(sp =>
|
||||
prefab.SpawnPos.HasFlag(sp.SpawnPosType) && (sp.Length >= prefab.MinSurfaceWidth && prefab.Alignment.HasFlag(sp.Alignment) || sp.SpawnPosType == LevelObjectPrefab.SpawnPosType.LevelEnd || sp.SpawnPosType == LevelObjectPrefab.SpawnPosType.LevelStart)).ToList());
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface && spawnPosition != null)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
|
||||
}
|
||||
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
Vector2 edgeDir = Vector2.UnitX;
|
||||
if (spawnPosition == null)
|
||||
{
|
||||
position = new Vector2(
|
||||
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
|
||||
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
var newObject = new LevelObject(prefab,
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
|
||||
AddObject(newObject, level);
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (prefab.MaxCount < amount)
|
||||
{
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
@@ -151,38 +149,119 @@ namespace Barotrauma
|
||||
availablePrefabs.Remove(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
|
||||
foreach (Level.Cave cave in level.Caves)
|
||||
{
|
||||
availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall)));
|
||||
availableSpawnPositions.Clear();
|
||||
suitableSpawnPositions.Clear();
|
||||
spawnPositionWeights.Clear();
|
||||
|
||||
var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
|
||||
List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
|
||||
foreach (var edge in caveCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
int childCount = Rand.Range(child.MinCount, child.MaxCount, Rand.RandSync.Server);
|
||||
for (int j = 0; j < childCount; j++)
|
||||
{
|
||||
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
int prefabCount = matchingPrefabs.Count();
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
|
||||
if (childPrefab == null) continue;
|
||||
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
|
||||
|
||||
var childObject = new LevelObject(childPrefab,
|
||||
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.Server)),
|
||||
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.Server),
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
|
||||
|
||||
AddObject(childObject, level);
|
||||
}
|
||||
if (!edge.NextToCave) { continue; }
|
||||
if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
|
||||
if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
|
||||
}
|
||||
}
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
|
||||
|
||||
for (int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (prefab.MaxCount < amount)
|
||||
{
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
{
|
||||
availablePrefabs.Remove(prefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level)
|
||||
{
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface && spawnPosition.Normal.LengthSquared() > 0.001f && spawnPosition != null)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
|
||||
}
|
||||
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
Vector2 edgeDir = Vector2.UnitX;
|
||||
if (spawnPosition == null)
|
||||
{
|
||||
position = new Vector2(
|
||||
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
|
||||
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
if (!MathUtils.NearlyEqual(prefab.RandomOffset.X, 0.0f) || !MathUtils.NearlyEqual(prefab.RandomOffset.Y, 0.0f))
|
||||
{
|
||||
Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.Server);
|
||||
position += offsetDir * Rand.Range(prefab.RandomOffset.X, prefab.RandomOffset.Y, Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
var newObject = new LevelObject(prefab,
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
|
||||
AddObject(newObject, level);
|
||||
|
||||
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
|
||||
{
|
||||
int childCount = Rand.Range(child.MinCount, child.MaxCount, Rand.RandSync.Server);
|
||||
for (int j = 0; j < childCount; j++)
|
||||
{
|
||||
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
int prefabCount = matchingPrefabs.Count();
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
|
||||
if (childPrefab == null) continue;
|
||||
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
|
||||
|
||||
var childObject = new LevelObject(childPrefab,
|
||||
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.Server)),
|
||||
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.Server),
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
|
||||
|
||||
AddObject(childObject, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddObject(LevelObject newObject, Level level)
|
||||
{
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
{
|
||||
trigger.OnTriggered += (levelTrigger, obj) =>
|
||||
if (newObject.Triggers != null)
|
||||
{
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
{
|
||||
OnObjectTriggered(newObject, levelTrigger, obj);
|
||||
};
|
||||
trigger.OnTriggered += (levelTrigger, obj) =>
|
||||
{
|
||||
OnObjectTriggered(newObject, levelTrigger, obj);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var spriteCorners = new List<Vector2>
|
||||
@@ -223,22 +302,24 @@ namespace Barotrauma
|
||||
float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z - level.BottomPos;
|
||||
float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z - level.BottomPos;
|
||||
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
if (newObject.Triggers != null)
|
||||
{
|
||||
if (trigger.PhysicsBody == null) continue;
|
||||
for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
|
||||
foreach (LevelTrigger trigger in newObject.Triggers)
|
||||
{
|
||||
trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
|
||||
if (trigger.PhysicsBody == null) { continue; }
|
||||
for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
|
||||
{
|
||||
trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
|
||||
|
||||
minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
|
||||
maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
|
||||
minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
|
||||
maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
|
||||
minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
|
||||
maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
|
||||
minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
|
||||
maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if CLIENT
|
||||
if (newObject.ParticleEmitters != null)
|
||||
{
|
||||
@@ -253,6 +334,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
objects.Add(newObject);
|
||||
if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
|
||||
newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
|
||||
|
||||
int xStart = (int)Math.Floor(minX / GridSize);
|
||||
@@ -290,7 +372,7 @@ namespace Barotrauma
|
||||
return objects;
|
||||
}
|
||||
|
||||
private readonly static List<LevelObject> objectsInRange = new List<LevelObject>();
|
||||
private readonly static HashSet<LevelObject> objectsInRange = new HashSet<LevelObject>();
|
||||
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
|
||||
{
|
||||
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
|
||||
@@ -309,10 +391,10 @@ namespace Barotrauma
|
||||
{
|
||||
for (int y = minIndices.Y; y <= maxIndices.Y; y++)
|
||||
{
|
||||
if (objectGrid[x, y] == null) continue;
|
||||
if (objectGrid[x, y] == null) { continue; }
|
||||
foreach (LevelObject obj in objectGrid[x, y])
|
||||
{
|
||||
if (!objectsInRange.Contains(obj)) objectsInRange.Add(obj);
|
||||
objectsInRange.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,12 +404,14 @@ namespace Barotrauma
|
||||
|
||||
private List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
|
||||
{
|
||||
List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid || edge.OutsideLevel) continue;
|
||||
if (!edge.IsSolid || edge.OutsideLevel) { continue; }
|
||||
if (spawnPosType != LevelObjectPrefab.SpawnPosType.CaveWall && edge.NextToCave) { continue; }
|
||||
Vector2 normal = edge.GetNormal(cell);
|
||||
|
||||
Alignment edgeAlignment = 0;
|
||||
@@ -340,7 +424,13 @@ namespace Barotrauma
|
||||
else if(normal.X > 0.5f)
|
||||
edgeAlignment |= Alignment.Right;
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosType, edgeAlignment));
|
||||
spawnPosTypes.Clear();
|
||||
spawnPosTypes.Add(spawnPosType);
|
||||
if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.MainPathWall) && edge.NextToMainPath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.MainPathWall); }
|
||||
if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.SidePathWall) && edge.NextToSidePath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.SidePathWall); }
|
||||
if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall) && edge.NextToCave) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.CaveWall); }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosTypes, edgeAlignment));
|
||||
}
|
||||
}
|
||||
return availableSpawnPositions;
|
||||
@@ -348,7 +438,7 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (LevelObject obj in objects)
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
@@ -361,21 +451,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.PhysicsBody != null)
|
||||
{
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered;
|
||||
obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = obj.PhysicsBody.Rotation;
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
|
||||
/*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = -obj.PhysicsBody.Rotation;*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,10 +479,10 @@ namespace Barotrauma
|
||||
|
||||
private void OnObjectTriggered(LevelObject triggeredObject, LevelTrigger trigger, Entity triggerer)
|
||||
{
|
||||
if (trigger.TriggerOthersDistance <= 0.0f) return;
|
||||
if (trigger.TriggerOthersDistance <= 0.0f) { return; }
|
||||
foreach (LevelObject obj in objects)
|
||||
{
|
||||
if (obj == triggeredObject) continue;
|
||||
if (obj == triggeredObject || obj.Triggers == null) { continue; }
|
||||
foreach (LevelTrigger otherTrigger in obj.Triggers)
|
||||
{
|
||||
otherTrigger.OtherTriggered(triggeredObject, trigger);
|
||||
@@ -397,16 +490,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(string levelType)
|
||||
{
|
||||
return GetRandomPrefab(levelType, LevelObjectPrefab.List);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(string levelType, IList<LevelObjectPrefab> availablePrefabs)
|
||||
private LevelObjectPrefab GetRandomPrefab(LevelGenerationParams generationParams, IList<LevelObjectPrefab> availablePrefabs)
|
||||
{
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(generationParams)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(levelType)).ToList(), Rand.RandSync.Server);
|
||||
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs)
|
||||
{
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(caveParams)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(caveParams)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
@@ -418,6 +515,7 @@ namespace Barotrauma
|
||||
obj.Remove();
|
||||
}
|
||||
objects.Clear();
|
||||
updateableObjects.Clear();
|
||||
}
|
||||
RemoveProjSpecific();
|
||||
|
||||
|
||||
+98
-39
@@ -8,11 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class LevelObjectPrefab : ISerializableEntity
|
||||
{
|
||||
private static List<LevelObjectPrefab> list = new List<LevelObjectPrefab>();
|
||||
public static List<LevelObjectPrefab> List
|
||||
{
|
||||
get { return list; }
|
||||
}
|
||||
public static List<LevelObjectPrefab> List { get; } = new List<LevelObjectPrefab>();
|
||||
|
||||
public class ChildObject
|
||||
{
|
||||
@@ -38,12 +34,15 @@ namespace Barotrauma
|
||||
public enum SpawnPosType
|
||||
{
|
||||
None = 0,
|
||||
Wall = 1,
|
||||
RuinWall = 2,
|
||||
SeaFloor = 4,
|
||||
MainPath = 8,
|
||||
LevelStart = 16,
|
||||
LevelEnd = 32,
|
||||
MainPathWall = 1,
|
||||
SidePathWall = 2,
|
||||
CaveWall = 4,
|
||||
RuinWall = 8,
|
||||
SeaFloor = 16,
|
||||
MainPath = 32,
|
||||
LevelStart = 64,
|
||||
LevelEnd = 128,
|
||||
Wall = MainPathWall | SidePathWall | CaveWall,
|
||||
}
|
||||
|
||||
public List<Sprite> Sprites
|
||||
@@ -52,12 +51,6 @@ namespace Barotrauma
|
||||
private set;
|
||||
} = new List<Sprite>();
|
||||
|
||||
public List<Sprite> SpecularSprites
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Sprite>();
|
||||
|
||||
public DeformableSprite DeformableSprite
|
||||
{
|
||||
get;
|
||||
@@ -117,7 +110,13 @@ namespace Barotrauma
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
} = -1;
|
||||
|
||||
public Dictionary<Sprite, XElement> SpriteSpecificPhysicsBodyElements
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<Sprite, XElement>();
|
||||
|
||||
|
||||
[Serialize(10000, false, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
@@ -162,6 +161,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0", true, description: "Random offset from the surface the object spawns on.")]
|
||||
public Vector2 RandomOffset
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true, description: "Should the object be rotated to align it with the wall surface it spawns on.")]
|
||||
public bool AlignWithSurface
|
||||
{
|
||||
@@ -243,12 +249,18 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
{
|
||||
get;
|
||||
@@ -272,12 +284,12 @@ namespace Barotrauma
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "LevelObjectPrefab (" + Name + ")";
|
||||
return "LevelObjectPrefab (" + Identifier + ")";
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
list.Clear();
|
||||
List.Clear();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelObjectPrefabs);
|
||||
if (files.Count() > 0)
|
||||
{
|
||||
@@ -303,35 +315,62 @@ namespace Barotrauma
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
DebugConsole.NewMessage($"Overriding all level object prefabs with '{configPath}'", Color.Yellow);
|
||||
list.Clear();
|
||||
List.Clear();
|
||||
}
|
||||
else if (list.Any())
|
||||
else if (List.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Loading additional level object prefabs from file '{configPath}'");
|
||||
DebugConsole.Log($"Loading additional level object prefabs from file '{configPath}'");
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
list.Add(new LevelObjectPrefab(element));
|
||||
var element = subElement.IsOverride() ? subElement.FirstElement() : subElement;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
var existingPrefab = List.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingPrefab != null)
|
||||
{
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing level object prefab '{identifier}' using the file '{configPath}'", Color.Yellow);
|
||||
List.Remove(existingPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{configPath}': Duplicate level object prefab '{identifier}' found in '{configPath}'! Each level object prefab must have a unique identifier. " +
|
||||
"Use <override></override> tags to override prefabs.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
List.Add(new LevelObjectPrefab(element));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(String.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
|
||||
DebugConsole.ThrowError(string.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
public LevelObjectPrefab(XElement element)
|
||||
public LevelObjectPrefab(XElement element, string identifier = null)
|
||||
{
|
||||
ChildObjects = new List<ChildObject>();
|
||||
LevelTriggerElements = new List<XElement>();
|
||||
OverrideProperties = new List<LevelObjectPrefab>();
|
||||
OverrideCommonness = new Dictionary<string, float>();
|
||||
|
||||
Identifier = null;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
if (element != null)
|
||||
{
|
||||
Config = element;
|
||||
Name = element.Name.ToString();
|
||||
Identifier = element.GetAttributeString("identifier", null) ?? identifier;
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#else
|
||||
DebugConsole.AddWarning($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#endif
|
||||
Identifier = element.Name.ToString();
|
||||
}
|
||||
LoadElements(element, -1);
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
@@ -346,21 +385,27 @@ namespace Barotrauma
|
||||
|
||||
private void LoadElements(XElement element, int parentTriggerIndex)
|
||||
{
|
||||
int propertyOverrideCount = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprites.Add( new Sprite(subElement, lazyLoad: true));
|
||||
break;
|
||||
case "specularsprite":
|
||||
SpecularSprites.Add(new Sprite(subElement, lazyLoad: true));
|
||||
var newSprite = new Sprite(subElement, lazyLoad: true);
|
||||
Sprites.Add(newSprite);
|
||||
var spriteSpecificPhysicsBodyElement =
|
||||
subElement.Element("PhysicsBody") ?? subElement.Element("Body") ??
|
||||
subElement.Element("physicsbody") ?? subElement.Element("body");
|
||||
if (spriteSpecificPhysicsBodyElement != null)
|
||||
{
|
||||
SpriteSpecificPhysicsBodyElements.Add(newSprite, spriteSpecificPhysicsBodyElement);
|
||||
}
|
||||
break;
|
||||
case "deformablesprite":
|
||||
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = subElement.GetAttributeString("leveltype", "");
|
||||
string levelType = subElement.GetAttributeString("leveltype", "").ToLowerInvariant();
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
||||
@@ -376,13 +421,14 @@ namespace Barotrauma
|
||||
ChildObjects.Add(new ChildObject(subElement));
|
||||
break;
|
||||
case "overrideproperties":
|
||||
var propertyOverride = new LevelObjectPrefab(subElement);
|
||||
var propertyOverride = new LevelObjectPrefab(subElement, identifier: Identifier + "-" + propertyOverrideCount);
|
||||
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
|
||||
if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
|
||||
{
|
||||
propertyOverride.Sprites = Sprites;
|
||||
propertyOverride.DeformableSprite = DeformableSprite;
|
||||
}
|
||||
propertyOverrideCount++;
|
||||
break;
|
||||
case "body":
|
||||
case "physicsbody":
|
||||
@@ -395,13 +441,26 @@ namespace Barotrauma
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public float GetCommonness(string levelType)
|
||||
|
||||
public float GetCommonness(CaveGenerationParams generationParams)
|
||||
{
|
||||
if (!OverrideCommonness.TryGetValue(levelType, out float commonness))
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return Commonness;
|
||||
return commonness;
|
||||
}
|
||||
return commonness;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
|
||||
(generationParams.OldIdentifier != null && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,18 @@ namespace Barotrauma
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string InfectIdentifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float InfectionChance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
|
||||
{
|
||||
@@ -211,6 +223,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
|
||||
|
||||
InfectIdentifier = element.GetAttributeString("infectidentifier", null);
|
||||
InfectionChance = element.GetAttributeFloat("infectionchance", 0.05f);
|
||||
|
||||
stayTriggeredDelay = element.GetAttributeFloat("staytriggereddelay", 0.0f);
|
||||
randomTriggerInterval = element.GetAttributeFloat("randomtriggerinterval", 0.0f);
|
||||
@@ -513,9 +528,14 @@ namespace Barotrauma
|
||||
float structureDamage = attack.GetStructureDamage(deltaTime);
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage);
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(InfectIdentifier))
|
||||
{
|
||||
submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance);
|
||||
}
|
||||
}
|
||||
|
||||
if (Force.LengthSquared() > 0.01f)
|
||||
@@ -598,7 +618,7 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 GetWaterFlowVelocity()
|
||||
{
|
||||
if (Force == Vector2.Zero) return Vector2.Zero;
|
||||
if (Force == Vector2.Zero || ForceMode == TriggerForceMode.LimitVelocity) { return Vector2.Zero; }
|
||||
|
||||
Vector2 vel = Force;
|
||||
if (ForceMode == TriggerForceMode.Acceleration)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Voronoi2;
|
||||
using System.Linq;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
@@ -11,18 +12,15 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelWall : IDisposable
|
||||
{
|
||||
private List<VoronoiCell> cells;
|
||||
public List<VoronoiCell> Cells
|
||||
{
|
||||
get { return cells; }
|
||||
}
|
||||
{
|
||||
public List<VoronoiCell> Cells { get; private set; }
|
||||
|
||||
private Body body;
|
||||
public Body Body
|
||||
{
|
||||
get { return body; }
|
||||
}
|
||||
public Body Body { get; private set; }
|
||||
|
||||
protected readonly Level level;
|
||||
|
||||
private readonly List<Vector2[]> triangles;
|
||||
private readonly Color color;
|
||||
|
||||
private float moveState;
|
||||
private float moveLength;
|
||||
@@ -38,6 +36,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float wallDamageOnTouch;
|
||||
public float WallDamageOnTouch
|
||||
{
|
||||
get { return wallDamageOnTouch; }
|
||||
set
|
||||
{
|
||||
Cells.ForEach(c => c.DoesDamage = !MathUtils.NearlyEqual(value, 0.0f));
|
||||
wallDamageOnTouch = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float MoveSpeed;
|
||||
|
||||
private Vector2? originalPos;
|
||||
@@ -50,30 +59,32 @@ namespace Barotrauma
|
||||
|
||||
public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false)
|
||||
{
|
||||
if (giftWrap)
|
||||
this.level = level;
|
||||
this.color = color;
|
||||
List<Vector2> originalVertices = new List<Vector2>(vertices);
|
||||
if (giftWrap) { vertices = MathUtils.GiftWrap(vertices); }
|
||||
if (vertices.Count < 3)
|
||||
{
|
||||
vertices = MathUtils.GiftWrap(vertices);
|
||||
throw new ArgumentException("Failed to generate a wall (not enough vertices). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString())));
|
||||
}
|
||||
|
||||
VoronoiCell wallCell = new VoronoiCell(vertices.ToArray());
|
||||
for (int i = 0; i < wallCell.Edges.Count; i++)
|
||||
{
|
||||
wallCell.Edges[i].Cell1 = wallCell;
|
||||
wallCell.Edges[i].IsSolid = true;
|
||||
}
|
||||
cells = new List<VoronoiCell>() { wallCell };
|
||||
|
||||
body = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles);
|
||||
Cells = new List<VoronoiCell>() { wallCell };
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
#if CLIENT
|
||||
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
|
||||
SetBodyVertices(bodyVertices.ToArray(), color);
|
||||
SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color);
|
||||
GenerateVertices();
|
||||
#endif
|
||||
}
|
||||
|
||||
public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level)
|
||||
{
|
||||
cells = new List<VoronoiCell>();
|
||||
this.level = level;
|
||||
this.color = color;
|
||||
Cells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < edgePositions.Count - 1; i++)
|
||||
{
|
||||
Vector2[] vertices = new Vector2[4];
|
||||
@@ -84,7 +95,7 @@ namespace Barotrauma
|
||||
|
||||
VoronoiCell wallCell = new VoronoiCell(vertices)
|
||||
{
|
||||
CellType = CellType.Edge
|
||||
CellType = CellType.Solid
|
||||
};
|
||||
wallCell.Edges[0].Cell1 = wallCell;
|
||||
wallCell.Edges[1].Cell1 = wallCell;
|
||||
@@ -94,31 +105,28 @@ namespace Barotrauma
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
wallCell.Edges[3].Cell2 = cells[i - 1];
|
||||
cells[i - 1].Edges[1].Cell2 = wallCell;
|
||||
wallCell.Edges[3].Cell2 = Cells[i - 1];
|
||||
Cells[i - 1].Edges[1].Cell2 = wallCell;
|
||||
}
|
||||
|
||||
cells.Add(wallCell);
|
||||
Cells.Add(wallCell);
|
||||
}
|
||||
|
||||
body = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles);
|
||||
body.CollisionCategories = Physics.CollisionLevel;
|
||||
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
Body.CollisionCategories = Physics.CollisionLevel;
|
||||
#if CLIENT
|
||||
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
|
||||
SetBodyVertices(bodyVertices.ToArray(), color);
|
||||
SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color);
|
||||
GenerateVertices();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (body.BodyType == BodyType.Static) return;
|
||||
if (Body.BodyType == BodyType.Static) { return; }
|
||||
|
||||
Vector2 bodyPos = ConvertUnits.ToDisplayUnits(body.Position);
|
||||
Vector2 bodyPos = ConvertUnits.ToDisplayUnits(Body.Position);
|
||||
Cells.ForEach(c => c.Translation = bodyPos);
|
||||
|
||||
if (!originalPos.HasValue) originalPos = bodyPos;
|
||||
if (!originalPos.HasValue) { originalPos = bodyPos; }
|
||||
|
||||
if (moveLength > 0.0f && MoveSpeed > 0.0f)
|
||||
{
|
||||
@@ -126,10 +134,15 @@ namespace Barotrauma
|
||||
moveState %= MathHelper.TwoPi;
|
||||
|
||||
Vector2 targetPos = ConvertUnits.ToSimUnits(originalPos.Value + moveAmount * (float)Math.Sin(moveState));
|
||||
body.ApplyForce((targetPos - body.Position).ClampLength(1.0f) * body.Mass);
|
||||
Body.ApplyForce((targetPos - Body.Position).ClampLength(1.0f) * Body.Mass);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPointInside(Vector2 point)
|
||||
{
|
||||
return Cells.Any(c => c.IsPointInside(point));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
@@ -139,16 +152,8 @@ namespace Barotrauma
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
#if CLIENT
|
||||
if (wallVertices != null)
|
||||
{
|
||||
wallVertices.Dispose();
|
||||
wallVertices = null;
|
||||
}
|
||||
if (bodyVertices != null)
|
||||
{
|
||||
BodyVertices.Dispose();
|
||||
bodyVertices = null;
|
||||
}
|
||||
VertexBuffer?.Dispose();
|
||||
VertexBuffer = null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
private string filePath;
|
||||
|
||||
private List<RuinRoom> roomTypeList;
|
||||
private readonly List<RuinRoom> roomTypeList;
|
||||
|
||||
public string Name => "RuinGenerationParams";
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public LinkedSubmarine(Submarine submarine)
|
||||
: base(null, submarine)
|
||||
public LinkedSubmarine(Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base(null, submarine, id)
|
||||
{
|
||||
linkedToID = new List<ushort>();
|
||||
|
||||
@@ -102,9 +102,9 @@ namespace Barotrauma
|
||||
return sl;
|
||||
}
|
||||
|
||||
public static LinkedSubmarine CreateDummy(Submarine mainSub, XElement element, Vector2 position)
|
||||
public static LinkedSubmarine CreateDummy(Submarine mainSub, XElement element, Vector2 position, ushort id = Entity.NullEntityID)
|
||||
{
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub, id);
|
||||
sl.GenerateWallVertices(element);
|
||||
if (sl.wallVertices.Any())
|
||||
{
|
||||
@@ -132,15 +132,7 @@ namespace Barotrauma
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
|
||||
var path = filePath;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
var linkedSubmarine = CreateDummy(Submarine, saveElement, Position);
|
||||
linkedSubmarine.saveElement = saveElement;
|
||||
return linkedSubmarine;
|
||||
}
|
||||
return CreateDummy(Submarine, path, Position);
|
||||
return CreateDummy(Submarine, filePath, Position);
|
||||
}
|
||||
|
||||
private void GenerateWallVertices(XElement rootElement)
|
||||
@@ -171,13 +163,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// LinkedSubmarine.Load() is called from MapEntity.LoadAll()
|
||||
public static LinkedSubmarine Load(XElement element, Submarine submarine)
|
||||
public static LinkedSubmarine Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
|
||||
LinkedSubmarine linkedSub;
|
||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
linkedSub = CreateDummy(submarine, element, pos);
|
||||
linkedSub = CreateDummy(submarine, element, pos, idRemap.AssignMaxId());
|
||||
linkedSub.saveElement = element;
|
||||
linkedSub.purchasedLostShuttles = false;
|
||||
}
|
||||
@@ -185,7 +177,7 @@ namespace Barotrauma
|
||||
{
|
||||
string levelSeed = element.GetAttributeString("location", "");
|
||||
LevelData levelData = GameMain.GameSession.Campaign?.NextLevel ?? GameMain.GameSession.LevelData;
|
||||
linkedSub = new LinkedSubmarine(submarine)
|
||||
linkedSub = new LinkedSubmarine(submarine, idRemap.AssignMaxId())
|
||||
{
|
||||
purchasedLostShuttles = GameMain.GameSession.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles,
|
||||
saveElement = element
|
||||
@@ -207,9 +199,9 @@ namespace Barotrauma
|
||||
int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]);
|
||||
for (int i = 0; i < linkedToIds.Length; i++)
|
||||
{
|
||||
linkedSub.linkedToID.Add((ushort)linkedToIds[i]);
|
||||
linkedSub.linkedToID.Add(idRemap.GetOffsetId(linkedToIds[i]));
|
||||
}
|
||||
linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0);
|
||||
linkedSub.originalLinkedToID = idRemap.GetOffsetId(element.GetAttributeInt("originallinkedto", 0));
|
||||
linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0);
|
||||
|
||||
return linkedSub.loadSub ? linkedSub : null;
|
||||
@@ -238,8 +230,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
sub = Submarine.Load(info, false);
|
||||
|
||||
IdRemap parentRemap = new IdRemap(Submarine.Info.SubmarineElement, Submarine.IdOffset);
|
||||
sub = Submarine.Load(info, false, parentRemap);
|
||||
|
||||
IdRemap childRemap = new IdRemap(saveElement, sub.IdOffset);
|
||||
|
||||
Vector2 worldPos = saveElement.GetAttributeVector2("worldpos", Vector2.Zero);
|
||||
if (worldPos != Vector2.Zero)
|
||||
{
|
||||
@@ -273,7 +268,8 @@ namespace Barotrauma
|
||||
}
|
||||
originalLinkedPort = linkedPort;
|
||||
|
||||
myPort = (FindEntityByID(originalMyPortID) as Item)?.GetComponent<DockingPort>();
|
||||
ushort originalMyId = childRemap.GetOffsetId(originalMyPortID);
|
||||
myPort = (FindEntityByID(originalMyId) as Item)?.GetComponent<DockingPort>();
|
||||
if (myPort == null)
|
||||
{
|
||||
float closestDistance = 0.0f;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
||||
{
|
||||
OriginalContainerID = item.OriginalContainerID;
|
||||
}
|
||||
OriginalID = item.OriginalID;
|
||||
OriginalID = item.ID;
|
||||
ModuleIndex = (ushort)item.OriginalModuleIndex;
|
||||
Identifier = item.prefab.Identifier;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return item.OriginalID == OriginalID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
return item.ID == OriginalID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,17 +76,6 @@ namespace Barotrauma
|
||||
|
||||
public LevelData LevelData { get; set; }
|
||||
|
||||
private float normalizedDepth;
|
||||
public float NormalizedDepth
|
||||
{
|
||||
get { return normalizedDepth; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
normalizedDepth = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public int PortraitId { get; private set; }
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
@@ -200,7 +189,6 @@ namespace Barotrauma
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
NormalizedDepth = element.GetAttributeFloat("normalizeddepth", 0.0f);
|
||||
TypeChangeTimer = element.GetAttributeInt("changetimer", 0);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
@@ -285,14 +273,15 @@ namespace Barotrauma
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
Name = Type.NameFormats == null ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
CreateStore(force: true);
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
availableMissions.Add(InstantiateMission(missionPrefab, connection));
|
||||
var mission = InstantiateMission(missionPrefab, connection);
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
@@ -309,7 +298,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
{
|
||||
@@ -342,7 +331,7 @@ namespace Barotrauma
|
||||
suitableMissions = unusedMissions;
|
||||
}
|
||||
MissionPrefab missionPrefab = suitableMissions.GetRandom();
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
{
|
||||
@@ -363,24 +352,31 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection = null)
|
||||
private Mission InstantiateMission(MissionPrefab prefab, out LocationConnection connection)
|
||||
{
|
||||
if (connection == null)
|
||||
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
|
||||
if (!suitableConnections.Any())
|
||||
{
|
||||
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
|
||||
if (!suitableConnections.Any())
|
||||
{
|
||||
suitableConnections = Connections;
|
||||
}
|
||||
//prefer connections that haven't been passed through, and connections with fewer available missions
|
||||
connection = ToolBox.SelectWeightedRandom(
|
||||
suitableConnections.ToList(),
|
||||
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
suitableConnections = Connections.ToList();
|
||||
}
|
||||
//prefer connections that haven't been passed through, and connections with fewer available missions
|
||||
connection = ToolBox.SelectWeightedRandom(
|
||||
suitableConnections.ToList(),
|
||||
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
|
||||
Location destination = connection.OtherLocation(this);
|
||||
return prefab.Instantiate(new Location[] { this, destination });
|
||||
var mission = prefab.Instantiate(new Location[] { this, destination });
|
||||
mission.AdjustLevelData(connection.LevelData);
|
||||
return mission;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection)
|
||||
{
|
||||
Location destination = connection.OtherLocation(this);
|
||||
var mission = prefab.Instantiate(new Location[] { this, destination });
|
||||
mission.AdjustLevelData(connection.LevelData);
|
||||
return mission;
|
||||
}
|
||||
|
||||
public void InstantiateLoadedMissions(Map map)
|
||||
@@ -499,7 +495,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (takenItems.Any(it => it.Matches(item) && it.OriginalID == item.OriginalID)) { continue; }
|
||||
if (takenItems.Any(it => it.Matches(item) && it.OriginalID == item.ID)) { continue; }
|
||||
if (item.OriginalModuleIndex < 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to register a non-outpost item as being taken from the outpost.");
|
||||
@@ -698,7 +694,6 @@ namespace Barotrauma
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("normalizeddepth", NormalizedDepth.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier));
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Barotrauma
|
||||
public readonly string Identifier;
|
||||
public readonly string Name;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
|
||||
|
||||
public bool UseInMainMenu
|
||||
@@ -74,6 +76,9 @@ namespace Barotrauma
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", element.Name.ToString());
|
||||
Name = TextManager.Get("LocationName." + Identifier, fallBackTag: "unknown");
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -7,9 +8,15 @@ namespace Barotrauma
|
||||
class LocationTypeChange
|
||||
{
|
||||
public readonly string ChangeToType;
|
||||
|
||||
public readonly float Probability;
|
||||
public readonly int RequiredDuration;
|
||||
|
||||
public readonly float ProximityProbabilityIncrease;
|
||||
public readonly int RequiredProximityForProbabilityIncrease;
|
||||
|
||||
public readonly bool RequireDiscovered;
|
||||
|
||||
public List<string> Messages = new List<string>();
|
||||
|
||||
//the change can't happen if there's a location of the given type next to this one
|
||||
@@ -24,6 +31,11 @@ namespace Barotrauma
|
||||
Probability = element.GetAttributeFloat("probability", 1.0f);
|
||||
RequiredDuration = element.GetAttributeInt("requiredduration", 0);
|
||||
|
||||
RequireDiscovered = element.GetAttributeBool("requirediscovered", false);
|
||||
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
|
||||
|
||||
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
|
||||
RequiredAdjacentLocations = element.GetAttributeStringArray("requiredadjacentlocations", new string[0]).ToList();
|
||||
|
||||
@@ -35,5 +47,49 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
|
||||
}
|
||||
}
|
||||
|
||||
public float DetermineProbability(Location location)
|
||||
{
|
||||
float totalProbability = Probability;
|
||||
if (AnyWithinRequiredProximity(location)) { totalProbability += ProximityProbabilityIncrease; }
|
||||
return totalProbability;
|
||||
}
|
||||
|
||||
private bool AnyWithinRequiredProximity(Location location, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > RequiredProximityForProbabilityIncrease) { return false; }
|
||||
if (currentDistance > 0 && RequiredAdjacentLocations.Contains(location.Type.Identifier)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinRequiredProximity(otherLocation, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int CountWithinRequiredProximity(Location location, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > RequiredProximityForProbabilityIncrease) { return 0; }
|
||||
int count = currentDistance > 0 && RequiredAdjacentLocations.Contains(location.Type.Identifier) ? 1 : 0;
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation)) { count += CountWithinRequiredProximity(otherLocation, currentDistance+1, checkedLocations); }
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace Barotrauma
|
||||
private Map(CampaignMode campaign, XElement element) : this()
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -80,11 +81,14 @@ namespace Barotrauma
|
||||
Locations.Add(null);
|
||||
}
|
||||
Locations[i] = new Location(subElement);
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -101,7 +105,10 @@ namespace Barotrauma
|
||||
Locations[locationIndices.Y].Connections.Add(connection);
|
||||
connection.LevelData = new LevelData(subElement.Element("Level"));
|
||||
string biomeId = subElement.GetAttributeString("biome", "");
|
||||
connection.Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeId) ?? LevelGenerationParams.GetBiomes().First();
|
||||
connection.Biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeId) ??
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.OldIdentifier == biomeId) ??
|
||||
LevelGenerationParams.GetBiomes().First();
|
||||
Connections.Add(connection);
|
||||
break;
|
||||
}
|
||||
@@ -354,9 +361,8 @@ namespace Barotrauma
|
||||
CreateEndLocation();
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
{
|
||||
location.LevelData = new LevelData(location);
|
||||
location.NormalizedDepth = location.MapPosition.X / Width;
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
@@ -672,9 +678,16 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Discovered) { continue; }
|
||||
if (furthestDiscoveredLocation == null ||
|
||||
location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.MapPosition.X < furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
@@ -682,10 +695,13 @@ namespace Barotrauma
|
||||
if (location == CurrentLocation || location == SelectedLocation) { continue; }
|
||||
|
||||
//find which types of locations this one can change to
|
||||
var cct = location.Type.CanChangeTo;
|
||||
List<LocationTypeChange> allowedTypeChanges = new List<LocationTypeChange>();
|
||||
List<LocationTypeChange> readyTypeChanges = new List<LocationTypeChange>();
|
||||
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
|
||||
List<int> readyTypeChanges = new List<int>();
|
||||
for (int i = 0; i < cct.Count; i++)
|
||||
{
|
||||
LocationTypeChange typeChange = cct[i];
|
||||
if (typeChange.RequireDiscovered && !location.Discovered) { continue; }
|
||||
//check if there are any adjacent locations that would prevent the change
|
||||
bool disallowedFound = false;
|
||||
foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations)
|
||||
@@ -714,22 +730,26 @@ namespace Barotrauma
|
||||
|
||||
if (location.TypeChangeTimer >= typeChange.RequiredDuration)
|
||||
{
|
||||
readyTypeChanges.Add(typeChange);
|
||||
readyTypeChanges.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
List<float> readyTypeProbabilities = readyTypeChanges.Select(i => cct[i].DetermineProbability(location)).ToList();
|
||||
//select a random type change
|
||||
if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(t => t.Probability))
|
||||
if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(i => readyTypeProbabilities[i]))
|
||||
{
|
||||
var selectedTypeChange =
|
||||
ToolBox.SelectWeightedRandom(readyTypeChanges, readyTypeChanges.Select(t => t.Probability).ToList(), Rand.RandSync.Unsynced);
|
||||
var selectedTypeChangeIndex =
|
||||
ToolBox.SelectWeightedRandom(
|
||||
readyTypeChanges,
|
||||
readyTypeChanges.Select(i => readyTypeProbabilities[i]).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
var selectedTypeChange = cct[selectedTypeChangeIndex];
|
||||
if (selectedTypeChange != null)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(selectedTypeChange.ChangeToType, StringComparison.OrdinalIgnoreCase)));
|
||||
ChangeLocationType(location, prevName, selectedTypeChange);
|
||||
location.TypeChangeTimer = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,14 @@ using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class MapEntity : Entity
|
||||
abstract partial class MapEntity : Entity, ISpatialEntity
|
||||
{
|
||||
public static List<MapEntity> mapEntityList = new List<MapEntity>();
|
||||
|
||||
public readonly MapEntityPrefab prefab;
|
||||
|
||||
protected List<ushort> linkedToID;
|
||||
public List<ushort> unresolvedLinkedToID;
|
||||
|
||||
/// <summary>
|
||||
/// List of upgrades this item has
|
||||
@@ -249,12 +250,55 @@ namespace Barotrauma
|
||||
get { return ""; }
|
||||
}
|
||||
|
||||
public MapEntity(MapEntityPrefab prefab, Submarine submarine) : base(submarine)
|
||||
private bool ignoreByAI;
|
||||
public bool IgnoreByAI => ignoreByAI;
|
||||
public void SetIgnoreByAI(bool ignore) => ignoreByAI = ignore;
|
||||
|
||||
public MapEntity(MapEntityPrefab prefab, Submarine submarine, ushort id) : base(submarine, id)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
Scale = prefab != null ? prefab.Scale : 1;
|
||||
}
|
||||
|
||||
protected void ParseLinks(XElement element, IdRemap idRemap)
|
||||
{
|
||||
string linkedToString = element.GetAttributeString("linked", "");
|
||||
if (!string.IsNullOrEmpty(linkedToString))
|
||||
{
|
||||
string[] linkedToIds = linkedToString.Split(',');
|
||||
for (int i = 0; i < linkedToIds.Length; i++)
|
||||
{
|
||||
int srcId = int.Parse(linkedToIds[i]);
|
||||
int targetId = idRemap.GetOffsetId(srcId);
|
||||
if (targetId <= 0)
|
||||
{
|
||||
unresolvedLinkedToID ??= new List<ushort>();
|
||||
unresolvedLinkedToID.Add((ushort)srcId);
|
||||
continue;
|
||||
}
|
||||
linkedToID.Add((ushort)targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResolveLinks(IdRemap childRemap)
|
||||
{
|
||||
if (unresolvedLinkedToID == null) { return; }
|
||||
for (int i=0;i<unresolvedLinkedToID.Count;i++)
|
||||
{
|
||||
int srcId = unresolvedLinkedToID[i];
|
||||
int targetId = childRemap.GetOffsetId(srcId);
|
||||
if (targetId > 0)
|
||||
{
|
||||
var otherEntity = FindEntityByID((ushort)targetId) as MapEntity;
|
||||
linkedTo.Add(otherEntity);
|
||||
if (otherEntity.Linkable && otherEntity.linkedTo != null) otherEntity.linkedTo.Add(this);
|
||||
unresolvedLinkedToID.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
@@ -555,8 +599,10 @@ namespace Barotrauma
|
||||
Move(-relative * 2.0f);
|
||||
}
|
||||
|
||||
public static List<MapEntity> LoadAll(Submarine submarine, XElement parentElement, string filePath)
|
||||
public static List<MapEntity> LoadAll(Submarine submarine, XElement parentElement, string filePath, int idOffset)
|
||||
{
|
||||
IdRemap idRemap = new IdRemap(parentElement, idOffset);
|
||||
|
||||
List<MapEntity> entities = new List<MapEntity>();
|
||||
foreach (XElement element in parentElement.Elements())
|
||||
{
|
||||
@@ -580,7 +626,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo loadMethod = t.GetMethod("Load", new[] { typeof(XElement), typeof(Submarine) });
|
||||
MethodInfo loadMethod = t.GetMethod("Load", new[] { typeof(XElement), typeof(Submarine), typeof(IdRemap) });
|
||||
if (loadMethod == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");
|
||||
@@ -591,7 +637,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine });
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine, idRemap });
|
||||
if (newEntity != null) entities.Add((MapEntity)newEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +254,16 @@ namespace Barotrauma
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
}
|
||||
|
||||
//try to search based on identifier first
|
||||
if (string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
foreach (MapEntityPrefab prefab in List)
|
||||
{
|
||||
if (prefab.identifier == identifier) { return prefab; }
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MapEntityPrefab prefab in List)
|
||||
{
|
||||
if (identifier != null)
|
||||
|
||||
@@ -216,11 +216,13 @@ namespace Barotrauma
|
||||
List<MapEntity> loadEntities(Submarine sub)
|
||||
{
|
||||
Dictionary<PlacedModule, List<MapEntity>> entities = new Dictionary<PlacedModule, List<MapEntity>>();
|
||||
int idOffset = sub.IdOffset;
|
||||
for (int i = 0; i < selectedModules.Count; i++)
|
||||
{
|
||||
var selectedModule = selectedModules[i];
|
||||
sub.Info.GameVersion = selectedModule.Info.GameVersion;
|
||||
var moduleEntities = MapEntity.LoadAll(sub, selectedModule.Info.SubmarineElement, selectedModule.Info.FilePath);
|
||||
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)
|
||||
@@ -958,7 +960,7 @@ namespace Barotrauma
|
||||
return placedEntities;
|
||||
}
|
||||
|
||||
var moduleEntities = MapEntity.LoadAll(sub, hallwayInfo.SubmarineElement, hallwayInfo.FilePath);
|
||||
var moduleEntities = MapEntity.LoadAll(sub, hallwayInfo.SubmarineElement, hallwayInfo.FilePath, -1);
|
||||
|
||||
//remove items that don't fit in the hallway
|
||||
moduleEntities.Where(e => e is Item item && item.GetComponent<Door>() == null && e.Rect.Width > hallwayLength).ForEach(e => e.Remove());
|
||||
@@ -1418,7 +1420,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.BehaviorType;
|
||||
idleObjective.Behavior = humanPrefab.Behavior;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
|
||||
@@ -14,27 +14,31 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class WallSection
|
||||
partial class WallSection : ISpatialEntity
|
||||
{
|
||||
public Rectangle rect;
|
||||
public float damage;
|
||||
public Gap gap;
|
||||
private bool ignoreByAI;
|
||||
|
||||
public WallSection(Rectangle rect)
|
||||
public Structure Wall { get; }
|
||||
public Vector2 Position => Wall.SectionPosition(Wall.Sections.IndexOf(this));
|
||||
public Vector2 WorldPosition => Wall.SectionPosition(Wall.Sections.IndexOf(this), world: true);
|
||||
public Vector2 SimPosition => ConvertUnits.ToSimUnits(Position);
|
||||
public Submarine Submarine => Wall.Submarine;
|
||||
public Rectangle WorldRect => Submarine == null ? rect :
|
||||
new Rectangle((int)(rect.X + Submarine.Position.X), (int)(rect.Y + Submarine.Position.Y), rect.Width, rect.Height);
|
||||
public bool IgnoreByAI => ignoreByAI;
|
||||
|
||||
public WallSection(Rectangle rect, Structure wall, float damage = 0.0f)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
|
||||
|
||||
this.rect = rect;
|
||||
damage = 0.0f;
|
||||
this.damage = damage;
|
||||
Wall = wall;
|
||||
}
|
||||
|
||||
public WallSection(Rectangle rect, float damage)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
|
||||
|
||||
this.rect = rect;
|
||||
this.damage = 0.0f;
|
||||
}
|
||||
public void SetIgnoreByAI(bool ignore) => ignoreByAI = ignore;
|
||||
}
|
||||
|
||||
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
|
||||
@@ -108,7 +112,16 @@ namespace Barotrauma
|
||||
get => maxHealth ?? Prefab.Health;
|
||||
set => maxHealth = value;
|
||||
}
|
||||
|
||||
|
||||
private float crushDepth;
|
||||
|
||||
[Serialize(Level.DefaultRealWorldCrushDepth, true)]
|
||||
public float CrushDepth
|
||||
{
|
||||
get => crushDepth;
|
||||
set => crushDepth = Math.Max(value, Level.DefaultRealWorldCrushDepth);
|
||||
}
|
||||
|
||||
public float Health => MaxHealth;
|
||||
|
||||
public override bool DrawBelowWater
|
||||
@@ -131,10 +144,16 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
return Prefab.Body && !IsPlatform;
|
||||
return Prefab.Body && !IsPlatform;// && HasDamage;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasDamage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public StructurePrefab Prefab => prefab as StructurePrefab;
|
||||
|
||||
public HashSet<string> Tags
|
||||
@@ -343,8 +362,8 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine)
|
||||
: base(sp, submarine)
|
||||
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine, ushort id = Entity.NullEntityID, XElement element = null)
|
||||
: base(sp, submarine, id)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
|
||||
if (rectangle.Width == 0 || rectangle.Height == 0) { return; }
|
||||
@@ -382,7 +401,6 @@ namespace Barotrauma
|
||||
|
||||
StairDirection = Prefab.StairDirection;
|
||||
NoAITarget = Prefab.NoAITarget;
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
|
||||
InitProjSpecific();
|
||||
|
||||
@@ -399,7 +417,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
Sections = new WallSection[1];
|
||||
Sections[0] = new WallSection(rect);
|
||||
Sections[0] = new WallSection(rect, this);
|
||||
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
@@ -408,6 +426,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
|
||||
|
||||
// Only add ai targets automatically to submarine/outpost walls
|
||||
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget)
|
||||
{
|
||||
@@ -552,7 +572,7 @@ namespace Barotrauma
|
||||
//sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
|
||||
int xIndex = FlippedX && IsHorizontal ? (xsections - 1 - x) : x;
|
||||
int yIndex = FlippedY && !IsHorizontal ? (ysections - 1 - y) : y;
|
||||
Sections[xIndex + yIndex] = new WallSection(sectionRect);
|
||||
Sections[xIndex + yIndex] = new WallSection(sectionRect, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -560,7 +580,7 @@ namespace Barotrauma
|
||||
sectionRect.Width -= (int)Math.Max(sectionRect.Right - rect.Right, 0.0f);
|
||||
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
|
||||
|
||||
Sections[x + y] = new WallSection(sectionRect);
|
||||
Sections[x + y] = new WallSection(sectionRect, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -783,33 +803,36 @@ namespace Barotrauma
|
||||
|
||||
public void AddDamage(int sectionIndex, float damage, Character attacker = null)
|
||||
{
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible) return;
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible ) { return; }
|
||||
|
||||
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) return;
|
||||
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; }
|
||||
|
||||
var section = Sections[sectionIndex];
|
||||
|
||||
#if CLIENT
|
||||
float dmg = Math.Min(MaxHealth - section.damage, damage);
|
||||
float particleAmount = MathHelper.Lerp(0, 25, MathUtils.InverseLerp(0, 100, dmg * Rand.Range(0.75f, 1.25f)));
|
||||
// Special case for very low but frequent dmg like plasma cutter: 10% chance for emitting a particle
|
||||
if (particleAmount < 1 && Rand.Value() < 0.10f)
|
||||
if (damage > 0)
|
||||
{
|
||||
particleAmount = 1;
|
||||
}
|
||||
for (int i = 1; i <= particleAmount; i++)
|
||||
{
|
||||
Vector2 particlePos = new Vector2(
|
||||
Rand.Range(section.rect.X, section.rect.Right),
|
||||
Rand.Range(section.rect.Y - section.rect.Height, section.rect.Y));
|
||||
|
||||
if (Submarine != null)
|
||||
float dmg = Math.Min(MaxHealth - section.damage, damage);
|
||||
float particleAmount = MathHelper.Lerp(0, 25, MathUtils.InverseLerp(0, 100, dmg * Rand.Range(0.75f, 1.25f)));
|
||||
// Special case for very low but frequent dmg like plasma cutter: 10% chance for emitting a particle
|
||||
if (particleAmount < 1 && Rand.Value() < 0.10f)
|
||||
{
|
||||
particlePos += Submarine.DrawPosition;
|
||||
particleAmount = 1;
|
||||
}
|
||||
for (int i = 1; i <= particleAmount; i++)
|
||||
{
|
||||
Vector2 particlePos = new Vector2(
|
||||
Rand.Range(section.rect.X, section.rect.Right),
|
||||
Rand.Range(section.rect.Y - section.rect.Height, section.rect.Y));
|
||||
|
||||
if (Submarine != null)
|
||||
{
|
||||
particlePos += Submarine.DrawPosition;
|
||||
}
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)));
|
||||
if (particle == null) break;
|
||||
}
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)));
|
||||
if (particle == null) break;
|
||||
}
|
||||
#endif
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
@@ -895,15 +918,12 @@ namespace Barotrauma
|
||||
}
|
||||
return sectionPos;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode) return new AttackResult(0.0f, null);
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible) return new AttackResult(0.0f, null);
|
||||
if (Submarine != null && Submarine.GodMode) { return new AttackResult(0.0f, null); }
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible) { return new AttackResult(0.0f, null); }
|
||||
|
||||
Vector2 transformedPos = worldPosition;
|
||||
if (Submarine != null) transformedPos -= Submarine.Position;
|
||||
@@ -921,26 +941,24 @@ namespace Barotrauma
|
||||
GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#if CLIENT
|
||||
if (playSound)
|
||||
if (playSound && damageAmount > 0)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags);
|
||||
}
|
||||
#endif
|
||||
|
||||
return new AttackResult(damageAmount, null);
|
||||
}
|
||||
|
||||
private void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true)
|
||||
public void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode || Indestructible) { return; }
|
||||
if (!Prefab.Body) { return; }
|
||||
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)
|
||||
{
|
||||
@@ -1054,13 +1072,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float gapOpen = (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
}
|
||||
|
||||
float damageDiff = damage - Sections[sectionIndex].damage;
|
||||
bool hadHole = SectionBodyDisabled(sectionIndex);
|
||||
Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, MaxHealth);
|
||||
|
||||
HasDamage = Sections.Any(s => s.damage > 0.0f);
|
||||
|
||||
if (attacker != null && damageDiff != 0.0f)
|
||||
{
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
@@ -1077,7 +1096,7 @@ namespace Barotrauma
|
||||
|
||||
bool hasHole = SectionBodyDisabled(sectionIndex);
|
||||
|
||||
if (hadHole == hasHole) return;
|
||||
if (hadHole == hasHole) { return; }
|
||||
|
||||
UpdateSections();
|
||||
}
|
||||
@@ -1259,7 +1278,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static Structure Load(XElement element, Submarine submarine)
|
||||
public static Structure Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
@@ -1272,14 +1291,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
||||
Structure s = new Structure(rect, prefab, submarine)
|
||||
Structure s = new Structure(rect, prefab, submarine, idRemap.GetOffsetId(element), element)
|
||||
{
|
||||
Submarine = submarine,
|
||||
ID = (ushort)int.Parse(element.Attribute("ID").Value)
|
||||
};
|
||||
s.OriginalID = s.ID;
|
||||
|
||||
SerializableProperty.DeserializeProperties(s, element);
|
||||
|
||||
if (submarine?.Info.GameVersion != null)
|
||||
{
|
||||
|
||||
@@ -152,6 +152,47 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float? realWorldCrushDepth;
|
||||
public float RealWorldCrushDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!realWorldCrushDepth.HasValue)
|
||||
{
|
||||
realWorldCrushDepth = float.PositiveInfinity;
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
if (structure.Submarine != this || !structure.HasBody || structure.Indestructible) { continue; }
|
||||
realWorldCrushDepth = Math.Min(structure.CrushDepth, realWorldCrushDepth.Value);
|
||||
}
|
||||
if (Info.SubmarineClass == SubmarineClass.DeepDiver)
|
||||
{
|
||||
realWorldCrushDepth *= 1.2f;
|
||||
}
|
||||
}
|
||||
return realWorldCrushDepth.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How deep down the sub is from the surface of Europa in meters (affected by level type, does not correspond to "actual" coordinate systems)
|
||||
/// </summary>
|
||||
public float RealWorldDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Level.Loaded?.GenerationParams == null)
|
||||
{
|
||||
return -WorldPosition.Y * Physics.DisplayToRealWorldRatio;
|
||||
}
|
||||
else if (GameMain.GameSession?.Campaign == null)
|
||||
{
|
||||
return (-(WorldPosition.Y - Level.Loaded.GenerationParams.Height) + 80000.0f) * Physics.DisplayToRealWorldRatio;
|
||||
}
|
||||
return Level.Loaded.GetRealWorldDepth(WorldPosition.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AtEndPosition
|
||||
{
|
||||
get
|
||||
@@ -210,7 +251,11 @@ namespace Barotrauma
|
||||
|
||||
public bool AtDamageDepth
|
||||
{
|
||||
get { return subBody != null && subBody.AtDamageDepth; }
|
||||
get
|
||||
{
|
||||
if (Level.Loaded == null || subBody == null) { return false; }
|
||||
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -236,6 +281,44 @@ namespace Barotrauma
|
||||
return Math.Max(minPrice, (int)price);
|
||||
}
|
||||
|
||||
private float ballastFloraTimer;
|
||||
public void AttemptBallastFloraInfection(string identifier, float deltaTime, float probability)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
if (ballastFloraTimer < 1f)
|
||||
{
|
||||
ballastFloraTimer += deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
ballastFloraTimer = 0;
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) >= probability) { return; }
|
||||
|
||||
List<Pump> pumps = new List<Pump>();
|
||||
List<Item> allItems = GetItems(true);
|
||||
|
||||
bool anyHasTag = allItems.Any(i => i.HasTag("ballast"));
|
||||
|
||||
foreach (Item item in allItems)
|
||||
{
|
||||
if ((!anyHasTag || item.HasTag("ballast")) && item.GetComponent<Pump>() is { } pump)
|
||||
{
|
||||
if (pump.Infected) { continue; }
|
||||
pumps.Add(pump);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pumps.Any()) { return; }
|
||||
|
||||
Pump randomPump = pumps.GetRandom(Rand.RandSync.Unsynced);
|
||||
randomPump.Infected = true;
|
||||
randomPump.InfectIdentifier = identifier;
|
||||
#if SERVER
|
||||
randomPump.Item.CreateServerEvent(randomPump);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void MakeWreck()
|
||||
{
|
||||
Info.Type = SubmarineType.Wreck;
|
||||
@@ -732,7 +815,7 @@ namespace Barotrauma
|
||||
/// check visibility between two points (in sim units)
|
||||
/// </summary>
|
||||
/// <returns>a physics body that was between the points (or null)</returns>
|
||||
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true, bool ignoreDisabledWalls = true)
|
||||
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true, bool ignoreDisabledWalls = true, bool ignoreBranches = true)
|
||||
{
|
||||
Body closestBody = null;
|
||||
float closestFraction = 1.0f;
|
||||
@@ -753,6 +836,7 @@ namespace Barotrauma
|
||||
&& !fixture.CollisionCategories.HasFlag(Physics.CollisionWall)
|
||||
&& !fixture.CollisionCategories.HasFlag(Physics.CollisionRepair)) { return -1; }
|
||||
if (ignoreSubs && fixture.Body.UserData is Submarine) { return -1; }
|
||||
if (ignoreBranches && fixture.Body.UserData is VineTile) { return -1; }
|
||||
if (fixture.Body.UserData as string == "ruinroom") { return -1; }
|
||||
if (fixture.Body.UserData is Structure structure)
|
||||
{
|
||||
@@ -1171,10 +1255,12 @@ namespace Barotrauma
|
||||
return new Rectangle((int)bounds.X, (int)bounds.Y, (int)(bounds.Z - bounds.X), (int)(bounds.Y - bounds.W));
|
||||
}
|
||||
|
||||
public Submarine(SubmarineInfo info, bool showWarningMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null) : base(null)
|
||||
public Submarine(SubmarineInfo info, bool showWarningMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID)
|
||||
{
|
||||
Loading = true;
|
||||
|
||||
loaded.Add(this);
|
||||
|
||||
Info = new SubmarineInfo(info);
|
||||
|
||||
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
|
||||
@@ -1202,7 +1288,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Info.SubmarineElement != null)
|
||||
{
|
||||
newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath);
|
||||
newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath, IdOffset);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1211,6 +1297,15 @@ namespace Barotrauma
|
||||
newEntities.ForEach(me => me.Submarine = this);
|
||||
}
|
||||
|
||||
if (newEntities != null)
|
||||
{
|
||||
foreach (var e in newEntities)
|
||||
{
|
||||
if (linkedRemap != null) { e.ResolveLinks(linkedRemap); }
|
||||
e.unresolvedLinkedToID = null;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 center = Vector2.Zero;
|
||||
var matchingHulls = Hull.hullList.FindAll(h => h.Submarine == this);
|
||||
|
||||
@@ -1279,8 +1374,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
loaded.Add(this);
|
||||
|
||||
if (entityGrid != null)
|
||||
{
|
||||
Hull.EntityGrids.Remove(entityGrid);
|
||||
@@ -1290,7 +1383,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
|
||||
{
|
||||
if (MapEntity.mapEntityList[i].Submarine != this) continue;
|
||||
if (MapEntity.mapEntityList[i].Submarine != this) { continue; }
|
||||
MapEntity.mapEntityList[i].Move(HiddenSubPosition);
|
||||
}
|
||||
|
||||
@@ -1313,6 +1406,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
|
||||
{
|
||||
GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged += ResetCrushDepth;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.LightManager.OnMapLoaded();
|
||||
#endif
|
||||
@@ -1333,19 +1431,27 @@ namespace Barotrauma
|
||||
if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
ID = (ushort)(ushort.MaxValue - 1 - Submarine.loaded.IndexOf(this));
|
||||
}
|
||||
|
||||
public static Submarine Load(SubmarineInfo info, bool unloadPrevious)
|
||||
protected override ushort DetermineID(ushort id, Submarine submarine)
|
||||
{
|
||||
return (ushort)(ReservedIDStart - Submarine.loaded.Count);
|
||||
}
|
||||
|
||||
public static Submarine Load(SubmarineInfo info, bool unloadPrevious, IdRemap linkedRemap = null)
|
||||
{
|
||||
if (unloadPrevious) { Unload(); }
|
||||
|
||||
Submarine sub = new Submarine(info, false);
|
||||
Submarine sub = new Submarine(info, false, linkedRemap: linkedRemap);
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
||||
private void ResetCrushDepth()
|
||||
{
|
||||
realWorldCrushDepth = null;
|
||||
}
|
||||
|
||||
public static void RepositionEntities(Vector2 moveAmount, IEnumerable<MapEntity> entities)
|
||||
{
|
||||
if (moveAmount.LengthSquared() < 0.00001f) { return; }
|
||||
@@ -1444,7 +1550,7 @@ namespace Barotrauma
|
||||
|
||||
visibleEntities = null;
|
||||
|
||||
if (GameMain.GameScreen.Cam != null) GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
if (GameMain.GameScreen.Cam != null) { GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; }
|
||||
|
||||
RemoveAll();
|
||||
|
||||
@@ -1482,6 +1588,11 @@ namespace Barotrauma
|
||||
subBody?.Remove();
|
||||
subBody = null;
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
|
||||
{
|
||||
GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged -= ResetCrushDepth;
|
||||
}
|
||||
|
||||
if (entityGrid != null)
|
||||
{
|
||||
Hull.EntityGrids.Remove(entityGrid);
|
||||
@@ -1516,7 +1627,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<PathNode> obstructedNodes = new HashSet<PathNode>();
|
||||
private readonly Dictionary<Submarine, HashSet<PathNode>> obstructedNodes = new Dictionary<Submarine, HashSet<PathNode>>();
|
||||
|
||||
/// <summary>
|
||||
/// Permanently disables obstructed waypoints obstructed by the level.
|
||||
@@ -1553,7 +1664,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (otherSub == null) { return; }
|
||||
if (otherSub == this) { return; }
|
||||
// Check collisions to other subs. Currently only walls are taken into account.
|
||||
// Check collisions to other subs.
|
||||
foreach (var node in OutdoorNodes)
|
||||
{
|
||||
if (node == null || node.Waypoint == null) { continue; }
|
||||
@@ -1572,8 +1683,13 @@ namespace Barotrauma
|
||||
{
|
||||
connectedWp.isObstructed = true;
|
||||
wp.isObstructed = true;
|
||||
obstructedNodes.Add(node);
|
||||
obstructedNodes.Add(connection);
|
||||
if (!obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
|
||||
{
|
||||
nodes = new HashSet<PathNode>();
|
||||
obstructedNodes.Add(otherSub, nodes);
|
||||
}
|
||||
nodes.Add(node);
|
||||
nodes.Add(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1584,13 +1700,14 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Only affects temporarily disabled waypoints.
|
||||
/// </summary>
|
||||
public void EnableObstructedWaypoints()
|
||||
public void EnableObstructedWaypoints(Submarine otherSub)
|
||||
{
|
||||
foreach (var node in obstructedNodes)
|
||||
if (obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
|
||||
{
|
||||
node.Waypoint.isObstructed = false;
|
||||
nodes.ForEach(n => n.Waypoint.isObstructed = false);
|
||||
nodes.Clear();
|
||||
obstructedNodes.Remove(otherSub);
|
||||
}
|
||||
obstructedNodes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace Barotrauma
|
||||
const float VerticalDrag = 0.05f;
|
||||
const float MaxDrag = 0.1f;
|
||||
|
||||
public const float DamageDepth = -30000.0f;
|
||||
private const float ImpactDamageMultiplier = 10.0f;
|
||||
|
||||
//limbs with a mass smaller than this won't cause an impact when they hit the sub
|
||||
@@ -40,7 +39,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private float depthDamageTimer;
|
||||
private float depthDamageTimer = 10.0f;
|
||||
|
||||
private readonly Submarine submarine;
|
||||
|
||||
@@ -94,11 +93,6 @@ namespace Barotrauma
|
||||
get { return positionBuffer; }
|
||||
}
|
||||
|
||||
public bool AtDamageDepth
|
||||
{
|
||||
get { return Position.Y < DamageDepth; }
|
||||
}
|
||||
|
||||
public Submarine Submarine
|
||||
{
|
||||
get { return submarine; }
|
||||
@@ -275,7 +269,7 @@ namespace Barotrauma
|
||||
|
||||
if (impact.Target.UserData is VoronoiCell cell)
|
||||
{
|
||||
HandleLevelCollision(impact);
|
||||
HandleLevelCollision(impact, cell);
|
||||
}
|
||||
else if (impact.Target.Body.UserData is Structure)
|
||||
{
|
||||
@@ -451,29 +445,26 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateDepthDamage(float deltaTime)
|
||||
{
|
||||
if (Position.Y > DamageDepth) { return; }
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession.GameMode is TestGameMode) { return; }
|
||||
if (GameMain.GameSession?.GameMode is TestGameMode) { return; }
|
||||
#endif
|
||||
float depth = DamageDepth - Position.Y;
|
||||
if (Level.Loaded == null) { return; }
|
||||
float submarineDepth = submarine.RealWorldDepth;
|
||||
if (submarineDepth < Level.Loaded.RealWorldCrushDepth) { return; }
|
||||
|
||||
depthDamageTimer -= deltaTime;
|
||||
|
||||
if (depthDamageTimer > 0.0f) { return; }
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != submarine) { continue; }
|
||||
if (wall.Submarine != submarine || wall.CrushDepth > submarineDepth) { continue; }
|
||||
|
||||
if (wall.Health < depth * 0.01f)
|
||||
float pastCrushDepth = submarineDepth - wall.CrushDepth;
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, depth * 0.01f);
|
||||
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(depth * 0.001f, 50.0f));
|
||||
}
|
||||
}
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(pastCrushDepth * 0.001f, 50.0f));
|
||||
}
|
||||
}
|
||||
|
||||
depthDamageTimer = 10.0f;
|
||||
@@ -655,7 +646,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLevelCollision(Impact impact)
|
||||
private void HandleLevelCollision(Impact impact, VoronoiCell cell = null)
|
||||
{
|
||||
if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 10)
|
||||
{
|
||||
@@ -672,6 +663,22 @@ namespace Barotrauma
|
||||
dockedSub.SubBody.ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
|
||||
}
|
||||
|
||||
if (cell != null && cell.IsDestructible && wallImpact > 0.0f)
|
||||
{
|
||||
var hitWall = Level.Loaded?.ExtraWalls.Find(w => w.Cells.Contains(cell));
|
||||
if (hitWall != null && hitWall.WallDamageOnTouch > 0.0f)
|
||||
{
|
||||
var damagedStructures = Explosion.RangedStructureDamage(
|
||||
ConvertUnits.ToDisplayUnits(impact.ImpactPos),
|
||||
500.0f,
|
||||
hitWall.WallDamageOnTouch,
|
||||
levelWallDamage: 0.0f);
|
||||
#if CLIENT
|
||||
PlayDamageSounds(damagedStructures, impact.ImpactPos, wallImpact, "StructureSlash");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
int particleAmount = (int)Math.Min(wallImpact * 10.0f, 50);
|
||||
for (int i = 0; i < particleAmount; i++)
|
||||
@@ -836,34 +843,15 @@ namespace Barotrauma
|
||||
|
||||
item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f);
|
||||
}
|
||||
|
||||
|
||||
float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
|
||||
var damagedStructures = Explosion.RangedStructureDamage(
|
||||
ConvertUnits.ToDisplayUnits(impactPos),
|
||||
impact * 50.0f,
|
||||
applyDamage ? impact * ImpactDamageMultiplier : 0.0f);
|
||||
impact * 50.0f,
|
||||
dmg, dmg);
|
||||
|
||||
#if CLIENT
|
||||
//play a damage sound for the structure that took the most damage
|
||||
float maxDamage = 0.0f;
|
||||
Structure maxDamageStructure = null;
|
||||
foreach (KeyValuePair<Structure, float> structureDamage in damagedStructures)
|
||||
{
|
||||
if (maxDamageStructure == null || structureDamage.Value > maxDamage)
|
||||
{
|
||||
maxDamage = structureDamage.Value;
|
||||
maxDamageStructure = structureDamage.Key;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxDamageStructure != null)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound(
|
||||
"StructureBlunt",
|
||||
impact * 10.0f,
|
||||
ConvertUnits.ToDisplayUnits(impactPos),
|
||||
MathHelper.Lerp(2000.0f, 10000.0f, (impact - MinCollisionImpact) / 2.0f),
|
||||
maxDamageStructure.Tags);
|
||||
}
|
||||
PlayDamageSounds(damagedStructures, impactPos, impact, "StructureBlunt");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma
|
||||
HideInMenus = 2
|
||||
}
|
||||
|
||||
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck }
|
||||
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation }
|
||||
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
|
||||
|
||||
partial class SubmarineInfo : IDisposable
|
||||
@@ -517,7 +517,8 @@ namespace Barotrauma
|
||||
{
|
||||
var contentPackageSubs = ContentPackage.GetFilesOfType(
|
||||
GameMain.Config.AllEnabledPackages,
|
||||
ContentType.Submarine, ContentType.Outpost, ContentType.OutpostModule, ContentType.Wreck);
|
||||
ContentType.Submarine, ContentType.Outpost, ContentType.OutpostModule,
|
||||
ContentType.Wreck, ContentType.BeaconStation);
|
||||
|
||||
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -553,7 +554,7 @@ namespace Barotrauma
|
||||
subDirectories = Directory.GetDirectories(SavePath).Where(s =>
|
||||
{
|
||||
DirectoryInfo dir = new DirectoryInfo(s);
|
||||
return (dir.Attributes & System.IO.FileAttributes.Hidden) == 0;
|
||||
return !dir.Attributes.HasFlag(System.IO.FileAttributes.Hidden) && !dir.Name.StartsWith(".");
|
||||
}).ToArray();
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace Barotrauma
|
||||
|
||||
public Hull CurrentHull { get; private set; }
|
||||
|
||||
public Level.Tunnel Tunnel;
|
||||
|
||||
public SpawnType SpawnType
|
||||
{
|
||||
get { return spawnType; }
|
||||
@@ -105,8 +107,8 @@ namespace Barotrauma
|
||||
{
|
||||
}
|
||||
|
||||
public WayPoint(MapEntityPrefab prefab, Rectangle newRect, Submarine submarine)
|
||||
: base (prefab, submarine)
|
||||
public WayPoint(MapEntityPrefab prefab, Rectangle newRect, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base (prefab, submarine, id)
|
||||
{
|
||||
rect = newRect;
|
||||
idCardTags = new string[0];
|
||||
@@ -626,7 +628,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static WayPoint Load(XElement element, Submarine submarine)
|
||||
public static WayPoint Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
@@ -635,11 +637,7 @@ namespace Barotrauma
|
||||
|
||||
|
||||
Enum.TryParse(element.GetAttributeString("spawn", "Path"), out SpawnType spawnType);
|
||||
WayPoint w = new WayPoint(MapEntityPrefab.Find(null, spawnType == SpawnType.Path ? "waypoint" : "spawnpoint"), rect, submarine)
|
||||
{
|
||||
ID = (ushort)int.Parse(element.Attribute("ID").Value)
|
||||
};
|
||||
w.OriginalID = w.ID;
|
||||
WayPoint w = new WayPoint(MapEntityPrefab.Find(null, spawnType == SpawnType.Path ? "waypoint" : "spawnpoint"), rect, submarine, idRemap.GetOffsetId(element));
|
||||
w.spawnType = spawnType;
|
||||
|
||||
string idCardDescString = element.GetAttributeString("idcarddesc", "");
|
||||
@@ -663,14 +661,24 @@ namespace Barotrauma
|
||||
JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
w.ladderId = (ushort)element.GetAttributeInt("ladders", 0);
|
||||
w.gapId = (ushort)element.GetAttributeInt("gap", 0);
|
||||
|
||||
w.linkedToID = new List<ushort>();
|
||||
w.ladderId = idRemap.GetOffsetId(element.GetAttributeInt("ladders", 0));
|
||||
w.gapId = idRemap.GetOffsetId(element.GetAttributeInt("gap", 0));
|
||||
|
||||
int i = 0;
|
||||
while (element.Attribute("linkedto" + i) != null)
|
||||
{
|
||||
w.linkedToID.Add((ushort)int.Parse(element.Attribute("linkedto" + i).Value));
|
||||
int srcId = int.Parse(element.Attribute("linkedto" + i).Value);
|
||||
int destId = idRemap.GetOffsetId(srcId);
|
||||
if (destId > 0)
|
||||
{
|
||||
w.linkedToID.Add((ushort)destId);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.unresolvedLinkedToID ??= new List<ushort>();
|
||||
w.unresolvedLinkedToID.Add((ushort)srcId);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return w;
|
||||
|
||||
Reference in New Issue
Block a user