(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObject
|
||||
{
|
||||
public readonly LevelObjectPrefab Prefab;
|
||||
public Vector3 Position;
|
||||
|
||||
public float NetworkUpdateTimer;
|
||||
|
||||
public float Scale;
|
||||
|
||||
public float Rotation;
|
||||
|
||||
private int spriteIndex;
|
||||
|
||||
public LevelObjectPrefab ActivePrefab;
|
||||
|
||||
public PhysicsBody PhysicsBody
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<LevelTrigger> Triggers
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool NeedsNetworkSyncing
|
||||
{
|
||||
get { return Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); }
|
||||
}
|
||||
|
||||
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]; }
|
||||
}
|
||||
|
||||
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
{
|
||||
Triggers = new List<LevelTrigger>();
|
||||
|
||||
ActivePrefab = Prefab = prefab;
|
||||
Position = position;
|
||||
Scale = scale;
|
||||
Rotation = rotation;
|
||||
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
|
||||
|
||||
if (prefab.PhysicsBodyElement != null)
|
||||
{
|
||||
PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
|
||||
}
|
||||
|
||||
foreach (XElement triggerElement in prefab.LevelTriggerElements)
|
||||
{
|
||||
Vector2 triggerPosition = triggerElement.GetAttributeVector2("position", Vector2.Zero) * scale;
|
||||
|
||||
if (rotation != 0.0f)
|
||||
{
|
||||
var ca = (float)Math.Cos(rotation);
|
||||
var sa = (float)Math.Sin(rotation);
|
||||
|
||||
triggerPosition = new Vector2(
|
||||
ca * triggerPosition.X + sa * triggerPosition.Y,
|
||||
-sa * triggerPosition.X + ca * triggerPosition.Y);
|
||||
}
|
||||
|
||||
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];
|
||||
Triggers.Add(newTrigger);
|
||||
}
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f)
|
||||
{
|
||||
Vector2 emitterPos = localPosition * Scale;
|
||||
|
||||
if (Rotation != 0.0f || Prefab.SwingAmountRad != 0.0f)
|
||||
{
|
||||
float rot = Rotation + swingState * Prefab.SwingAmountRad;
|
||||
|
||||
var ca = (float)Math.Cos(rot);
|
||||
var sa = (float)Math.Sin(rot);
|
||||
|
||||
emitterPos = new Vector2(
|
||||
ca * emitterPos.X + sa * emitterPos.Y,
|
||||
-sa * emitterPos.X + ca * emitterPos.Y);
|
||||
}
|
||||
return new Vector2(Position.X, Position.Y) + emitterPos;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
RemoveProjSpecific();
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "LevelObject (" + ActivePrefab.Name + ")";
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
for (int j = 0; j < Triggers.Count; j++)
|
||||
{
|
||||
if (!Triggers[j].UseNetworkSyncing) continue;
|
||||
Triggers[j].ServerWrite(msg, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
#if CLIENT
|
||||
using Barotrauma.Particles;
|
||||
#endif
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObjectManager : Entity, IServerSerializable
|
||||
{
|
||||
const int GridSize = 2000;
|
||||
|
||||
private List<LevelObject> objects;
|
||||
private List<LevelObject>[,] objectGrid;
|
||||
|
||||
public LevelObjectManager() : base(null)
|
||||
{
|
||||
}
|
||||
|
||||
class SpawnPosition
|
||||
{
|
||||
public readonly GraphEdge GraphEdge;
|
||||
public readonly Vector2 Normal;
|
||||
public readonly LevelObjectPrefab.SpawnPosType SpawnPosType;
|
||||
public readonly Alignment Alignment;
|
||||
public readonly float Length;
|
||||
|
||||
public SpawnPosition(GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment)
|
||||
{
|
||||
GraphEdge = graphEdge;
|
||||
Normal = normal;
|
||||
SpawnPosType = spawnPosType;
|
||||
Alignment = alignment;
|
||||
|
||||
Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
|
||||
}
|
||||
|
||||
public float GetSpawnProbability(LevelObjectPrefab prefab)
|
||||
{
|
||||
if (prefab.ClusteringAmount <= 0.0f) return Length;
|
||||
|
||||
float noise = (float)(
|
||||
PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, prefab.ClusteringGroup) +
|
||||
PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 20000.0f, GraphEdge.Point1.Y / 20000.0f, prefab.ClusteringGroup));
|
||||
|
||||
return Length * (float)Math.Pow(noise, prefab.ClusteringAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public void PlaceObjects(Level level, int amount)
|
||||
{
|
||||
objectGrid = new List<LevelObject>[
|
||||
level.Size.X / GridSize,
|
||||
(level.Size.Y - level.BottomPos) / GridSize];
|
||||
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
var levelCells = level.GetAllCells();
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
|
||||
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(level.SeaFloor.Cells, LevelObjectPrefab.SpawnPosType.SeaFloor));
|
||||
|
||||
foreach (RuinGeneration.Ruin ruin in level.Ruins)
|
||||
{
|
||||
foreach (var ruinShape in ruin.RuinShapes)
|
||||
{
|
||||
foreach (var wall in ruinShape.Walls)
|
||||
{
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(wall.A, wall.B),
|
||||
(wall.A + wall.B) / 2.0f - ruinShape.Center,
|
||||
LevelObjectPrefab.SpawnPosType.RuinWall,
|
||||
ruinShape.GetLineAlignment(wall)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var posOfInterest in level.PositionsOfInterest)
|
||||
{
|
||||
if (posOfInterest.PositionType != Level.PositionType.MainPath) continue;
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(posOfInterest.Position.ToVector2(), posOfInterest.Position.ToVector2() + Vector2.UnitX),
|
||||
Vector2.UnitY,
|
||||
LevelObjectPrefab.SpawnPosType.MainPath,
|
||||
Alignment.Top));
|
||||
}
|
||||
|
||||
objects = new List<LevelObject>();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams.Name);
|
||||
|
||||
SpawnPosition spawnPosition = FindObjectPosition(availableSpawnPositions, level, prefab);
|
||||
|
||||
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);
|
||||
|
||||
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) =>
|
||||
{
|
||||
OnObjectTriggered(newObject, levelTrigger, obj);
|
||||
};
|
||||
}
|
||||
|
||||
var spriteCorners = new List<Vector2>
|
||||
{
|
||||
Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
|
||||
};
|
||||
|
||||
Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
|
||||
|
||||
//calculate the positions of the corners of the rotated sprite
|
||||
if (sprite != null)
|
||||
{
|
||||
Vector2 halfSize = sprite.size * newObject.Scale / 2;
|
||||
spriteCorners[0] = -halfSize;
|
||||
spriteCorners[1] = new Vector2(-halfSize.X, halfSize.Y);
|
||||
spriteCorners[2] = halfSize;
|
||||
spriteCorners[3] = new Vector2(halfSize.X, -halfSize.Y);
|
||||
|
||||
Vector2 pivotOffset = sprite.Origin * newObject.Scale - halfSize;
|
||||
pivotOffset.X = -pivotOffset.X;
|
||||
pivotOffset = new Vector2(
|
||||
(float)(pivotOffset.X * Math.Cos(-newObject.Rotation) - pivotOffset.Y * Math.Sin(-newObject.Rotation)),
|
||||
(float)(pivotOffset.X * Math.Sin(-newObject.Rotation) + pivotOffset.Y * Math.Cos(-newObject.Rotation)));
|
||||
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
spriteCorners[j] = new Vector2(
|
||||
(float)(spriteCorners[j].X * Math.Cos(-newObject.Rotation) - spriteCorners[j].Y * Math.Sin(-newObject.Rotation)),
|
||||
(float)(spriteCorners[j].X * Math.Sin(-newObject.Rotation) + spriteCorners[j].Y * Math.Cos(-newObject.Rotation)));
|
||||
|
||||
spriteCorners[j] += new Vector2(newObject.Position.X, newObject.Position.Y) + pivotOffset;
|
||||
}
|
||||
}
|
||||
|
||||
float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z;
|
||||
float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z;
|
||||
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if CLIENT
|
||||
if (newObject.ParticleEmitters != null)
|
||||
{
|
||||
foreach (ParticleEmitter emitter in newObject.ParticleEmitters)
|
||||
{
|
||||
Rectangle particleBounds = emitter.CalculateParticleBounds(new Vector2(newObject.Position.X, newObject.Position.Y));
|
||||
minX = Math.Min(minX, particleBounds.X);
|
||||
maxX = Math.Max(maxX, particleBounds.Right);
|
||||
minY = Math.Min(minY, particleBounds.Y - level.BottomPos);
|
||||
maxY = Math.Max(maxY, particleBounds.Bottom - level.BottomPos);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
objects.Add(newObject);
|
||||
newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
|
||||
|
||||
int xStart = (int)Math.Floor(minX / GridSize);
|
||||
int xEnd = (int)Math.Floor(maxX / GridSize);
|
||||
if (xEnd < 0 || xStart >= objectGrid.GetLength(0)) return;
|
||||
|
||||
int yStart = (int)Math.Floor(minY / GridSize);
|
||||
int yEnd = (int)Math.Floor(maxY / GridSize);
|
||||
if (yEnd < 0 || yStart >= objectGrid.GetLength(1)) return;
|
||||
|
||||
xStart = Math.Max(xStart, 0);
|
||||
xEnd = Math.Min(xEnd, objectGrid.GetLength(0) - 1);
|
||||
yStart = Math.Max(yStart, 0);
|
||||
yEnd = Math.Min(yEnd, objectGrid.GetLength(1) - 1);
|
||||
|
||||
for (int x = xStart; x <= xEnd; x++)
|
||||
{
|
||||
for (int y = yStart; y <= yEnd; y++)
|
||||
{
|
||||
if (objectGrid[x, y] == null) objectGrid[x, y] = new List<LevelObject>();
|
||||
objectGrid[x, y].Add(newObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Microsoft.Xna.Framework.Point GetGridIndices(Vector2 worldPosition)
|
||||
{
|
||||
return new Microsoft.Xna.Framework.Point(
|
||||
(int)Math.Floor(worldPosition.X / GridSize),
|
||||
(int)Math.Floor((worldPosition.Y - Level.Loaded.BottomPos) / GridSize));
|
||||
}
|
||||
|
||||
public IEnumerable<LevelObject> GetAllObjects()
|
||||
{
|
||||
return objects;
|
||||
}
|
||||
|
||||
private readonly static List<LevelObject> objectsInRange = new List<LevelObject>();
|
||||
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
|
||||
{
|
||||
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
|
||||
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) return Enumerable.Empty<LevelObject>();
|
||||
|
||||
var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
|
||||
if (maxIndices.X < 0 || maxIndices.Y < 0) return Enumerable.Empty<LevelObject>();
|
||||
|
||||
minIndices.X = Math.Max(0, minIndices.X);
|
||||
minIndices.Y = Math.Max(0, minIndices.Y);
|
||||
maxIndices.X = Math.Min(objectGrid.GetLength(0) - 1, maxIndices.X);
|
||||
maxIndices.Y = Math.Min(objectGrid.GetLength(1) - 1, maxIndices.Y);
|
||||
|
||||
objectsInRange.Clear();
|
||||
for (int x = minIndices.X; x <= maxIndices.X; x++)
|
||||
{
|
||||
for (int y = minIndices.Y; y <= maxIndices.Y; y++)
|
||||
{
|
||||
if (objectGrid[x, y] == null) continue;
|
||||
foreach (LevelObject obj in objectGrid[x, y])
|
||||
{
|
||||
if (!objectsInRange.Contains(obj)) objectsInRange.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objectsInRange;
|
||||
}
|
||||
|
||||
private List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
|
||||
{
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid || edge.OutsideLevel) continue;
|
||||
Vector2 normal = edge.GetNormal(cell);
|
||||
|
||||
Alignment edgeAlignment = 0;
|
||||
if (normal.Y < -0.5f)
|
||||
edgeAlignment |= Alignment.Bottom;
|
||||
else if (normal.Y > 0.5f)
|
||||
edgeAlignment |= Alignment.Top;
|
||||
else if (normal.X < -0.5f)
|
||||
edgeAlignment |= Alignment.Left;
|
||||
else if(normal.X > 0.5f)
|
||||
edgeAlignment |= Alignment.Right;
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosType, edgeAlignment));
|
||||
}
|
||||
}
|
||||
return availableSpawnPositions;
|
||||
}
|
||||
|
||||
private SpawnPosition FindObjectPosition(List<SpawnPosition> availableSpawnPositions, Level level, LevelObjectPrefab prefab)
|
||||
{
|
||||
if (prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.None) return null;
|
||||
|
||||
var suitableSpawnPositions = availableSpawnPositions.Where(sp =>
|
||||
prefab.SpawnPos.HasFlag(sp.SpawnPosType) && sp.Length >= prefab.MinSurfaceWidth && prefab.Alignment.HasFlag(sp.Alignment)).ToList();
|
||||
|
||||
return ToolBox.SelectWeightedRandom(suitableSpawnPositions, suitableSpawnPositions.Select(sp => sp.GetSpawnProbability(prefab)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (LevelObject obj in objects)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
obj.NetworkUpdateTimer -= deltaTime;
|
||||
if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { obj });
|
||||
obj.NeedsNetworkSyncing = false;
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; 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;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
private void OnObjectTriggered(LevelObject triggeredObject, LevelTrigger trigger, Entity triggerer)
|
||||
{
|
||||
if (trigger.TriggerOthersDistance <= 0.0f) return;
|
||||
foreach (LevelObject obj in objects)
|
||||
{
|
||||
if (obj == triggeredObject) continue;
|
||||
foreach (LevelTrigger otherTrigger in obj.Triggers)
|
||||
{
|
||||
otherTrigger.OtherTriggered(triggeredObject, trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(string levelType)
|
||||
{
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
LevelObjectPrefab.List,
|
||||
LevelObjectPrefab.List.Select(p => p.GetCommonness(levelType)).ToList(), Rand.RandSync.Server);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
if (objects != null)
|
||||
{
|
||||
foreach (LevelObject obj in objects)
|
||||
{
|
||||
obj.Remove();
|
||||
}
|
||||
objects.Clear();
|
||||
}
|
||||
RemoveProjSpecific();
|
||||
|
||||
base.Remove();
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
LevelObject obj = extraData[0] as LevelObject;
|
||||
msg.WriteRangedInteger(objects.IndexOf(obj), 0, objects.Count);
|
||||
obj.ServerWrite(msg, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObjectPrefab : ISerializableEntity
|
||||
{
|
||||
private static List<LevelObjectPrefab> list = new List<LevelObjectPrefab>();
|
||||
public static List<LevelObjectPrefab> List
|
||||
{
|
||||
get { return list; }
|
||||
}
|
||||
|
||||
public class ChildObject
|
||||
{
|
||||
public List<string> AllowedNames;
|
||||
public int MinCount, MaxCount;
|
||||
|
||||
public ChildObject()
|
||||
{
|
||||
AllowedNames = new List<string>();
|
||||
MinCount = 1;
|
||||
MaxCount = 1;
|
||||
}
|
||||
|
||||
public ChildObject(XElement element)
|
||||
{
|
||||
AllowedNames = element.GetAttributeStringArray("names", new string[0]).ToList();
|
||||
MinCount = element.GetAttributeInt("mincount", 1);
|
||||
MaxCount = Math.Max(element.GetAttributeInt("maxcount", 1), MinCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum SpawnPosType
|
||||
{
|
||||
None = 0,
|
||||
Wall = 1,
|
||||
RuinWall = 2,
|
||||
SeaFloor = 4,
|
||||
MainPath = 8
|
||||
}
|
||||
|
||||
public List<Sprite> Sprites
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Sprite>();
|
||||
|
||||
public List<Sprite> SpecularSprites
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Sprite>();
|
||||
|
||||
public DeformableSprite DeformableSprite
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
public float MinSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
public float MaxSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which sides of a wall the object can appear on.
|
||||
/// </summary>
|
||||
[Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), true, description: "Which sides of a wall the object can spawn on."), Editable]
|
||||
public Alignment Alignment
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(SpawnPosType.Wall, false), Editable()]
|
||||
public SpawnPosType SpawnPos
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public XElement Config
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public readonly List<XElement> LevelTriggerElements;
|
||||
|
||||
/// <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 Dictionary<string, float> OverrideCommonness;
|
||||
|
||||
public XElement PhysicsBodyElement
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int PhysicsBodyTriggerIndex
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0.0,1.0", true), Editable]
|
||||
public Vector2 DepthRange
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f),
|
||||
Serialize(0.0f, true, description: "The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are used to determine the probability for an object to spawn at a specific position.")]
|
||||
/// <summary>
|
||||
/// The tendency for the prefab to form clusters. Used as an exponent for perlin noise values
|
||||
/// that are used to determine the probability for an object to spawn at a specific position.
|
||||
/// </summary>
|
||||
public float ClusteringAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f),
|
||||
Serialize(0.0f, true, description: "A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the probability " +
|
||||
" for an object to spawn at a specific position. Using the same (or close) value for different objects means the objects tend " +
|
||||
"to form clusters in the same areas.")]
|
||||
/// <summary>
|
||||
/// A value between 0-1 that determines the z-coordinate to sample perlin noise from when
|
||||
/// determining the probability for an object to spawn at a specific position.
|
||||
/// Using the same (or close) value for different objects means the objects tend to form clusters
|
||||
/// in the same areas.
|
||||
/// </summary>
|
||||
public float ClusteringGroup
|
||||
{
|
||||
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
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
/// <summary>
|
||||
/// Minimum length of a graph edge the object can spawn on.
|
||||
/// </summary>
|
||||
public float MinSurfaceWidth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Vector2 randomRotation;
|
||||
[Editable, Serialize("0.0,0.0", true, description: "How much the rotation of the object can vary (min and max values in degrees).")]
|
||||
public Vector2 RandomRotation
|
||||
{
|
||||
get { return new Vector2(MathHelper.ToDegrees(randomRotation.X), MathHelper.ToDegrees(randomRotation.Y)); }
|
||||
private set
|
||||
{
|
||||
randomRotation = new Vector2(MathHelper.ToRadians(value.X), MathHelper.ToRadians(value.Y));
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 RandomRotationRad => randomRotation;
|
||||
|
||||
private float swingAmount;
|
||||
[Serialize(0.0f, true, description: "How much the object swings (in degrees)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f)]
|
||||
public float SwingAmount
|
||||
{
|
||||
get { return MathHelper.ToDegrees(swingAmount); }
|
||||
private set
|
||||
{
|
||||
swingAmount = MathHelper.ToRadians(value);
|
||||
}
|
||||
}
|
||||
|
||||
public float SwingAmountRad => swingAmount;
|
||||
|
||||
[Serialize(0.0f, true, description: "How fast the object swings."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float SwingFrequency
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", true, description: "How much the scale of the object oscillates on each axis. A value of 0.5,0.5 would make the object's scale oscillate from 100% to 150%.")]
|
||||
public Vector2 ScaleOscillation
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "How fast the object's scale oscillates."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float ScaleOscillationFrequency
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(1.0f, true, description: "How likely it is for the object to spawn in a level. " +
|
||||
"This is relative to the commonness of the other objects - for example, having an object with " +
|
||||
"a commonness of 1 and another with a commonness of 10 would mean the latter appears in levels 10 times as frequently as the former. " +
|
||||
"The commonness value can be overridden on specific level types.")]
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "How much the object disrupts submarine's sonar."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float SonarDisruption
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of prefabs whose properties override this one's properties when a trigger is active.
|
||||
/// E.g. if a trigger in the index 1 of the trigger list is active, the properties in index 1 in this list are used (unless it's null)
|
||||
/// </summary>
|
||||
public List<LevelObjectPrefab> OverrideProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "LevelObjectPrefab (" + Name + ")";
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
list.Clear();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelObjectPrefabs);
|
||||
if (files.Count() > 0)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
LoadConfig(file.Path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadConfig("Content/LevelObjects/LevelObject/Prefabs.xml");
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadConfig(string configPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configPath);
|
||||
if (doc == null) { return; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
DebugConsole.NewMessage($"Overriding all level object prefabs with '{configPath}'", Color.Yellow);
|
||||
list.Clear();
|
||||
}
|
||||
else if (list.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Loading additional level object prefabs from file '{configPath}'");
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
list.Add(new LevelObjectPrefab(element));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(String.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
public LevelObjectPrefab(XElement element)
|
||||
{
|
||||
ChildObjects = new List<ChildObject>();
|
||||
LevelTriggerElements = new List<XElement>();
|
||||
OverrideProperties = new List<LevelObjectPrefab>();
|
||||
OverrideCommonness = new Dictionary<string, float>();
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
if (element != null)
|
||||
{
|
||||
Config = element;
|
||||
Name = element.Name.ToString();
|
||||
LoadElements(element, -1);
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
//use the maximum width of the sprite as the minimum surface width if no value is given
|
||||
if (element != null && !element.Attributes("minsurfacewidth").Any())
|
||||
{
|
||||
if (Sprites.Any()) MinSurfaceWidth = Sprites[0].size.X * MaxSize;
|
||||
if (DeformableSprite != null) MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadElements(XElement element, int parentTriggerIndex)
|
||||
{
|
||||
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));
|
||||
break;
|
||||
case "deformablesprite":
|
||||
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = subElement.GetAttributeString("leveltype", "");
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
||||
}
|
||||
break;
|
||||
case "leveltrigger":
|
||||
case "trigger":
|
||||
OverrideProperties.Add(null);
|
||||
LevelTriggerElements.Add(subElement);
|
||||
LoadElements(subElement, LevelTriggerElements.Count - 1);
|
||||
break;
|
||||
case "childobject":
|
||||
ChildObjects.Add(new ChildObject(subElement));
|
||||
break;
|
||||
case "overrideproperties":
|
||||
var propertyOverride = new LevelObjectPrefab(subElement);
|
||||
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
|
||||
if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
|
||||
{
|
||||
propertyOverride.Sprites = Sprites;
|
||||
propertyOverride.DeformableSprite = DeformableSprite;
|
||||
}
|
||||
break;
|
||||
case "body":
|
||||
case "physicsbody":
|
||||
PhysicsBodyElement = subElement;
|
||||
PhysicsBodyTriggerIndex = parentTriggerIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public float GetCommonness(string levelType)
|
||||
{
|
||||
if (!OverrideCommonness.TryGetValue(levelType, out float commonness))
|
||||
{
|
||||
return Commonness;
|
||||
}
|
||||
return commonness;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,612 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelTrigger
|
||||
{
|
||||
[Flags]
|
||||
enum TriggererType
|
||||
{
|
||||
None = 0,
|
||||
Human = 1,
|
||||
Creature = 2,
|
||||
Character = Human | Creature,
|
||||
Submarine = 4,
|
||||
Item = 8,
|
||||
OtherTrigger = 16
|
||||
}
|
||||
|
||||
public enum TriggerForceMode
|
||||
{
|
||||
Force, //default, apply a force to the object over time
|
||||
Acceleration, //apply an acceleration to the object, ignoring it's mass
|
||||
Impulse, //apply an instant force, ignoring deltaTime
|
||||
LimitVelocity //clamp the velocity of the triggerer to some value
|
||||
}
|
||||
|
||||
public Action<LevelTrigger, Entity> OnTriggered;
|
||||
|
||||
private PhysicsBody physicsBody;
|
||||
|
||||
/// <summary>
|
||||
/// Effects applied to entities that are inside the trigger
|
||||
/// </summary>
|
||||
private List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
|
||||
/// <summary>
|
||||
/// Attacks applied to entities that are inside the trigger
|
||||
/// </summary>
|
||||
private List<Attack> attacks = new List<Attack>();
|
||||
|
||||
private float cameraShake;
|
||||
private Vector2 unrotatedForce;
|
||||
private float forceFluctuationTimer, currentForceFluctuation = 1.0f;
|
||||
|
||||
private HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
|
||||
private TriggererType triggeredBy;
|
||||
|
||||
private float randomTriggerInterval;
|
||||
private float randomTriggerProbability;
|
||||
private float randomTriggerTimer;
|
||||
|
||||
private float triggeredTimer;
|
||||
|
||||
//how far away this trigger can activate other triggers from
|
||||
private float triggerOthersDistance;
|
||||
|
||||
private HashSet<string> tags = new HashSet<string>();
|
||||
|
||||
//other triggers have to have at least one of these tags to trigger this one
|
||||
private HashSet<string> allowedOtherTriggerTags = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// How long the trigger stays in the triggered state after triggerers have left
|
||||
/// </summary>
|
||||
private float stayTriggeredDelay;
|
||||
|
||||
public LevelTrigger ParentTrigger;
|
||||
|
||||
public Dictionary<Entity, Vector2> TriggererPosition
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Vector2 worldPosition;
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return worldPosition; }
|
||||
set
|
||||
{
|
||||
worldPosition = value;
|
||||
physicsBody?.SetTransform(ConvertUnits.ToSimUnits(value), physicsBody.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return physicsBody == null ? 0.0f : physicsBody.Rotation; }
|
||||
set
|
||||
{
|
||||
if (physicsBody == null) return;
|
||||
physicsBody.SetTransform(physicsBody.Position, value);
|
||||
CalculateDirectionalForce();
|
||||
}
|
||||
}
|
||||
|
||||
public PhysicsBody PhysicsBody
|
||||
{
|
||||
get { return physicsBody; }
|
||||
}
|
||||
|
||||
public float TriggerOthersDistance
|
||||
{
|
||||
get { return triggerOthersDistance; }
|
||||
}
|
||||
|
||||
public IEnumerable<Entity> Triggerers
|
||||
{
|
||||
get { return triggerers.AsEnumerable(); }
|
||||
}
|
||||
|
||||
public bool IsTriggered
|
||||
{
|
||||
get
|
||||
{
|
||||
return (triggerers.Count > 0 || triggeredTimer > 0.0f) &&
|
||||
(ParentTrigger == null || ParentTrigger.IsTriggered);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Force
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// does the force diminish by distance
|
||||
/// </summary>
|
||||
public bool ForceFalloff
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float ForceFluctuationInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public float ForceFluctuationStrength
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private TriggerForceMode forceMode;
|
||||
public TriggerForceMode ForceMode
|
||||
{
|
||||
get { return forceMode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop applying forces to objects if they're moving faster than this
|
||||
/// </summary>
|
||||
public float ForceVelocityLimit
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float ColliderRadius
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
public bool UseNetworkSyncing
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool NeedsNetworkSyncing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
|
||||
{
|
||||
TriggererPosition = new Dictionary<Entity, Vector2>();
|
||||
|
||||
worldPosition = position;
|
||||
if (element.Attributes("radius").Any() || element.Attributes("width").Any() || element.Attributes("height").Any())
|
||||
{
|
||||
physicsBody = new PhysicsBody(element, scale)
|
||||
{
|
||||
CollisionCategories = Physics.CollisionLevel,
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall
|
||||
};
|
||||
physicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
|
||||
physicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
|
||||
physicsBody.FarseerBody.SetIsSensor(true);
|
||||
physicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
physicsBody.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
|
||||
ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
|
||||
|
||||
physicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
|
||||
}
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
|
||||
|
||||
stayTriggeredDelay = element.GetAttributeFloat("staytriggereddelay", 0.0f);
|
||||
randomTriggerInterval = element.GetAttributeFloat("randomtriggerinterval", 0.0f);
|
||||
randomTriggerProbability = element.GetAttributeFloat("randomtriggerprobability", 0.0f);
|
||||
|
||||
UseNetworkSyncing = element.GetAttributeBool("networksyncing", false);
|
||||
|
||||
unrotatedForce =
|
||||
element.Attribute("force") != null && element.Attribute("force").Value.Contains(',') ?
|
||||
element.GetAttributeVector2("force", Vector2.Zero) :
|
||||
new Vector2(element.GetAttributeFloat("force", 0.0f), 0.0f);
|
||||
|
||||
ForceFluctuationInterval = element.GetAttributeFloat("forcefluctuationinterval", 0.01f);
|
||||
ForceFluctuationStrength = Math.Max(element.GetAttributeFloat("forcefluctuationstrength", 0.0f), 0.0f);
|
||||
ForceFalloff = element.GetAttributeBool("forcefalloff", true);
|
||||
|
||||
ForceVelocityLimit = ConvertUnits.ToSimUnits(element.GetAttributeFloat("forcevelocitylimit", float.MaxValue));
|
||||
string forceModeStr = element.GetAttributeString("forcemode", "Force");
|
||||
if (!Enum.TryParse(forceModeStr, out forceMode))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + forceModeStr + "\" is not a valid force mode.");
|
||||
}
|
||||
CalculateDirectionalForce();
|
||||
|
||||
string triggeredByStr = element.GetAttributeString("triggeredby", "Character");
|
||||
if (!Enum.TryParse(triggeredByStr, out triggeredBy))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type.");
|
||||
}
|
||||
UpdateCollisionCategories();
|
||||
triggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
|
||||
|
||||
var tagsArray = element.GetAttributeStringArray("tags", new string[0]);
|
||||
foreach (string tag in tagsArray)
|
||||
{
|
||||
tags.Add(tag.ToLower());
|
||||
}
|
||||
|
||||
if (triggeredBy.HasFlag(TriggererType.OtherTrigger))
|
||||
{
|
||||
var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", new string[0]);
|
||||
foreach (string tag in otherTagsArray)
|
||||
{
|
||||
allowedOtherTriggerTags.Add(tag.ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
statusEffects.Add(StatusEffect.Load(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in "+ parentDebugName));
|
||||
break;
|
||||
case "attack":
|
||||
case "damage":
|
||||
var attack = new Attack(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in " + parentDebugName);
|
||||
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
|
||||
attack.Afflictions.Clear();
|
||||
foreach (Affliction affliction in multipliedAfflictions)
|
||||
{
|
||||
attack.Afflictions.Add(affliction, null);
|
||||
}
|
||||
attacks.Add(attack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
forceFluctuationTimer = Rand.Range(0.0f, ForceFluctuationInterval);
|
||||
randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval);
|
||||
}
|
||||
|
||||
private void UpdateCollisionCategories()
|
||||
{
|
||||
if (physicsBody == null) return;
|
||||
|
||||
var collidesWith = Physics.CollisionNone;
|
||||
if (triggeredBy.HasFlag(TriggererType.Character) || triggeredBy.HasFlag(TriggererType.Creature)) collidesWith |= Physics.CollisionCharacter;
|
||||
if (triggeredBy.HasFlag(TriggererType.Item)) collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile;
|
||||
if (triggeredBy.HasFlag(TriggererType.Submarine)) collidesWith |= Physics.CollisionWall;
|
||||
|
||||
physicsBody.CollidesWith = collidesWith;
|
||||
}
|
||||
|
||||
private void CalculateDirectionalForce()
|
||||
{
|
||||
var ca = (float)Math.Cos(-Rotation);
|
||||
var sa = (float)Math.Sin(-Rotation);
|
||||
|
||||
Force = new Vector2(
|
||||
ca * unrotatedForce.X + sa * unrotatedForce.Y,
|
||||
-sa * unrotatedForce.X + ca * unrotatedForce.Y);
|
||||
}
|
||||
|
||||
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
|
||||
{
|
||||
Entity entity = GetEntity(fixtureB);
|
||||
if (entity == null) return false;
|
||||
|
||||
if (entity is Character character)
|
||||
{
|
||||
if (character.CurrentHull != null) return false;
|
||||
if (character.IsHuman)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Human)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Creature)) return false;
|
||||
}
|
||||
}
|
||||
else if (entity is Item item)
|
||||
{
|
||||
if (item.CurrentHull != null) return false;
|
||||
if (!triggeredBy.HasFlag(TriggererType.Item)) return false;
|
||||
}
|
||||
else if (entity is Submarine)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.Submarine)) return false;
|
||||
}
|
||||
|
||||
if (!triggerers.Contains(entity))
|
||||
{
|
||||
if (!IsTriggered)
|
||||
{
|
||||
OnTriggered?.Invoke(this, entity);
|
||||
}
|
||||
TriggererPosition[entity] = entity.WorldPosition;
|
||||
triggerers.Add(entity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact)
|
||||
{
|
||||
Entity entity = GetEntity(fixtureB);
|
||||
if (entity == null) return;
|
||||
|
||||
if (entity is Character character &&
|
||||
(!character.Enabled || character.Removed) &&
|
||||
triggerers.Contains(entity))
|
||||
{
|
||||
TriggererPosition.Remove(entity);
|
||||
triggerers.Remove(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
//check if there are any other contacts with the entity
|
||||
//(the OnSeparation callback happens when two fixtures separate,
|
||||
//e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
|
||||
ContactEdge contactEdge = fixtureA.Body.ContactList;
|
||||
while (contactEdge != null)
|
||||
{
|
||||
if (contactEdge.Contact != null &&
|
||||
contactEdge.Contact.IsTouching)
|
||||
{
|
||||
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
|
||||
contactEdge.Contact.FixtureB :
|
||||
contactEdge.Contact.FixtureA);
|
||||
if (otherEntity == entity) return;
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
|
||||
if (triggerers.Contains(entity))
|
||||
{
|
||||
TriggererPosition.Remove(entity);
|
||||
triggerers.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private Entity GetEntity(Fixture fixture)
|
||||
{
|
||||
if (fixture.Body == null || fixture.Body.UserData == null) return null;
|
||||
if (fixture.Body.UserData is Entity entity) return entity;
|
||||
if (fixture.Body.UserData is Limb limb) return limb.character;
|
||||
if (fixture.Body.UserData is SubmarineBody subBody) return subBody.Submarine;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Another trigger was triggered, check if this one should react to it
|
||||
/// </summary>
|
||||
public void OtherTriggered(LevelObject levelObject, LevelTrigger otherTrigger)
|
||||
{
|
||||
if (!triggeredBy.HasFlag(TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) return;
|
||||
|
||||
//check if the other trigger has appropriate tags
|
||||
if (allowedOtherTriggerTags.Count > 0)
|
||||
{
|
||||
if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) return;
|
||||
}
|
||||
|
||||
if (Vector2.DistanceSquared(WorldPosition, otherTrigger.WorldPosition) <= otherTrigger.triggerOthersDistance * otherTrigger.triggerOthersDistance)
|
||||
{
|
||||
bool wasAlreadyTriggered = IsTriggered;
|
||||
triggeredTimer = stayTriggeredDelay;
|
||||
if (!wasAlreadyTriggered)
|
||||
{
|
||||
OnTriggered?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (ParentTrigger != null && !ParentTrigger.IsTriggered) return;
|
||||
|
||||
triggerers.RemoveWhere(t => t.Removed);
|
||||
|
||||
bool isNotClient = true;
|
||||
#if CLIENT
|
||||
isNotClient = GameMain.Client == null;
|
||||
#endif
|
||||
|
||||
if (!UseNetworkSyncing || isNotClient)
|
||||
{
|
||||
if (ForceFluctuationStrength > 0.0f)
|
||||
{
|
||||
//no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers
|
||||
if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any())
|
||||
{
|
||||
forceFluctuationTimer += deltaTime;
|
||||
if (forceFluctuationTimer > ForceFluctuationInterval)
|
||||
{
|
||||
NeedsNetworkSyncing = true;
|
||||
currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
|
||||
forceFluctuationTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (randomTriggerProbability > 0.0f)
|
||||
{
|
||||
randomTriggerTimer += deltaTime;
|
||||
if (randomTriggerTimer > randomTriggerInterval)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability)
|
||||
{
|
||||
NeedsNetworkSyncing = true;
|
||||
triggeredTimer = stayTriggeredDelay;
|
||||
}
|
||||
randomTriggerTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stayTriggeredDelay > 0.0f)
|
||||
{
|
||||
if (triggerers.Count == 0)
|
||||
{
|
||||
triggeredTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
triggeredTimer = stayTriggeredDelay;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Entity triggerer in triggerers)
|
||||
{
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (triggerer is Character)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, (Character)triggerer);
|
||||
}
|
||||
else if (triggerer is Item)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, ((Item)triggerer).AllPropertyObjects);
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerer is IDamageable damageable)
|
||||
{
|
||||
foreach (Attack attack in attacks)
|
||||
{
|
||||
attack.DoDamage(null, damageable, WorldPosition, deltaTime, false);
|
||||
}
|
||||
}
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
foreach (Attack attack in attacks)
|
||||
{
|
||||
float structureDamage = attack.GetStructureDamage(deltaTime);
|
||||
if (structureDamage > 0.0f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Force.LengthSquared() > 0.01f)
|
||||
{
|
||||
if (triggerer is Character character)
|
||||
{
|
||||
ApplyForce(character.AnimController.Collider, deltaTime);
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
ApplyForce(limb.body, deltaTime);
|
||||
}
|
||||
}
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
ApplyForce(submarine.SubBody.Body, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerer == Character.Controlled || triggerer == Character.Controlled?.Submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyForce(PhysicsBody body, float deltaTime)
|
||||
{
|
||||
float distFactor = 1.0f;
|
||||
if (ForceFalloff)
|
||||
{
|
||||
distFactor = 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(body.SimPosition, PhysicsBody.SimPosition)) / ColliderRadius;
|
||||
if (distFactor < 0.0f) return;
|
||||
}
|
||||
|
||||
switch (ForceMode)
|
||||
{
|
||||
case TriggerForceMode.Force:
|
||||
if (ForceVelocityLimit < 1000.0f)
|
||||
body.ApplyForce(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
|
||||
else
|
||||
body.ApplyForce(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
break;
|
||||
case TriggerForceMode.Acceleration:
|
||||
if (ForceVelocityLimit < 1000.0f)
|
||||
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, ForceVelocityLimit);
|
||||
else
|
||||
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
break;
|
||||
case TriggerForceMode.Impulse:
|
||||
if (ForceVelocityLimit < 1000.0f)
|
||||
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: ForceVelocityLimit);
|
||||
else
|
||||
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
break;
|
||||
case TriggerForceMode.LimitVelocity:
|
||||
float maxVel = ForceVelocityLimit * currentForceFluctuation * distFactor;
|
||||
if (body.LinearVelocity.LengthSquared() > maxVel * maxVel)
|
||||
{
|
||||
body.ApplyForce(
|
||||
Vector2.Normalize(-body.LinearVelocity) *
|
||||
Force.Length() * body.Mass * currentForceFluctuation * distFactor,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
|
||||
{
|
||||
Vector2 baseVel = GetWaterFlowVelocity();
|
||||
if (baseVel.LengthSquared() < 0.1f) return Vector2.Zero;
|
||||
|
||||
float triggerSize = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
|
||||
float dist = Vector2.Distance(viewPosition, WorldPosition);
|
||||
if (dist > triggerSize) return Vector2.Zero;
|
||||
|
||||
return baseVel * (1.0f - dist / triggerSize);
|
||||
}
|
||||
|
||||
public Vector2 GetWaterFlowVelocity()
|
||||
{
|
||||
if (Force == Vector2.Zero) return Vector2.Zero;
|
||||
|
||||
Vector2 vel = Force;
|
||||
if (ForceMode == TriggerForceMode.Acceleration)
|
||||
{
|
||||
vel *= 1000.0f;
|
||||
}
|
||||
else if (ForceMode == TriggerForceMode.Impulse)
|
||||
{
|
||||
vel /= (float)Timing.Step;
|
||||
}
|
||||
return vel.ClampLength(ConvertUnits.ToDisplayUnits(ForceVelocityLimit)) * currentForceFluctuation;
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
if (ForceFluctuationStrength > 0.0f)
|
||||
{
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(currentForceFluctuation, 0.0f, 1.0f), 0.0f, 1.0f, 8);
|
||||
}
|
||||
if (stayTriggeredDelay > 0.0f)
|
||||
{
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(triggeredTimer, 0.0f, stayTriggeredDelay), 0.0f, stayTriggeredDelay, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user