226 lines
7.8 KiB
C#
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();
|
|
}
|
|
|
|
}
|
|
}
|