Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/DestructibleLevelWall.cs
2024-04-24 18:09:05 +03:00

226 lines
7.8 KiB
C#

using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
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 * 0.5f, 50.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, Vector2 impulseDirection, float deltaTime, bool playSound = true)
{
AddDamage(attack.StructureDamage, worldPosition);
return new AttackResult(attack.StructureDamage);
}
private void CreateFragments()
{
#if CLIENT
SoundPlayer.PlaySound("icebreak", WorldPosition);
#endif
Vector2 center = Vector2.Zero;
//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);
}
center += cell.Center;
}
if (Cells.Any())
{
center /= Cells.Count;
}
//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);
Level.Loaded.UnsyncedExtraWalls.Add(fragment);
#if CLIENT
for (int i = 0; i < 5; i++)
{
int startEdgeIndex = Rand.Int(3);
Vector2 pos1 = triangle[startEdgeIndex];
Vector2 pos2 = triangle[(startEdgeIndex + 1) % 3];
var particle = GameMain.ParticleManager.CreateParticle("iceexplosion",
triangleCenter + Vector2.Lerp(pos1, pos2, Rand.Range(0.0f, 1.0f)),
velocity: (Rand.Vector(Rand.Range(50.0f, 1000.0f)) + fragment.Body.LinearVelocity * 100.0f));
if (particle != null)
{
particle.Size *= Rand.Range(1.0f, 5.0f);
particle.ColorMultiplier *= Rand.Range(0.7f, 1.0f);
}
}
#endif
}
}
public void Destroy()
{
if (Destroyed) { return; }
Destroyed = true;
level?.UnsyncedExtraWalls?.Remove(this);
foreach (var cell in Cells)
{
cell.CellType = CellType.Removed;
cell.OnDestroyed?.Invoke();
cell.OnDestroyed = null;
}
GameMain.World.Remove(Body);
Dispose();
}
}
}