Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Map/Structure.cs
T
Joonas Rikkonen 5dc31c213f ad0bbaf...7245c72
commit 7245c721339885d062567befc052a592391b3b4a
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sun Mar 10 15:22:31 2019 +0200

    Fixed StatusEffects only applying afflictions to one limb even if the target is "Character" instead of "Limb", added a subtle screen distortion effect to heavy radiation sickness. Closes #1256

commit e0db27e62ec9546fd4b182a0cc97f7e5830645ae
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 21:53:51 2019 +0200

    Fixed WrapText adding unnecessary spaces after every line break. Closes #1215

commit 988bc58d51c195ad9265b84a1e97e0101cd3f808
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 21:12:50 2019 +0200

    Fixed crashing when attempting to create a body for a wall section that's less than 1 unit long (e.g. if a wall that's just slightly longer than the wall section size receives damage).

commit 8c31157425a9e2ec02312618d1bfa359ab3ee87d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 20:30:44 2019 +0200

    Fixed clients being unable to toggle the respawn shuttle on/off

commit a4ccb039219830efe9cd305c26942dda1bd04e9c
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 19:33:22 2019 +0200

    Fixed inability to select the respawn shuttle as a client host

commit b89b2d2c282d8c74d7ccd37b3f29dcab51eff680
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 19:32:41 2019 +0200

    Made it possible to edit the style of the ListBox under GUIDropDowns, increased the opacity of the listbox to make the contents more readable when there's text behind it

commit 8f6d9aef3d637fe37a18c78f4b15ef8fd266374e
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Sat Mar 9 18:11:23 2019 +0200

    Fixed NetLobbyScreen not showing the names of the submarines the client doesn't have
2019-03-18 22:39:57 +02:00

1198 lines
44 KiB
C#

using Barotrauma.Networking;
using Barotrauma.Extensions;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Factories;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class WallSection
{
public Rectangle rect;
public float damage;
public Gap gap;
public WallSection(Rectangle rect)
{
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
this.rect = rect;
damage = 0.0f;
}
public WallSection(Rectangle rect, float damage)
{
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
this.rect = rect;
this.damage = 0.0f;
}
}
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
{
public const int WallSectionSize = 96;
public static List<Structure> WallList = new List<Structure>();
//how much mechanic skill increases per damage removed from the wall by welding
public const float SkillIncreaseMultiplier = 0.005f;
const float LeakThreshold = 0.1f;
private SpriteEffects SpriteEffects = SpriteEffects.None;
//dimensions of the wall sections' physics bodies (only used for debug rendering)
private List<Vector2> bodyDebugDimensions = new List<Vector2>();
public bool Indestructible;
//sections of the wall that are supposed to be rendered
public WallSection[] Sections
{
get;
private set;
}
public override Sprite Sprite
{
get { return prefab.sprite; }
}
public bool IsPlatform
{
get { return Prefab.Platform; }
}
public Direction StairDirection
{
get;
private set;
}
public override string Name
{
get { return prefab.Name; }
}
public bool HasBody
{
get { return Prefab.Body; }
}
public List<Body> Bodies { get; private set; }
public bool CastShadow
{
get { return Prefab.CastShadow; }
}
public bool IsHorizontal { get; private set; }
public int SectionCount
{
get { return Sections.Length; }
}
public float Health
{
get { return Prefab.Health; }
}
public override bool DrawBelowWater
{
get
{
return base.DrawBelowWater || Prefab.BackgroundSprite != null;
}
}
public override bool DrawOverWater
{
get
{
return !DrawDamageEffect;
}
}
public override bool DrawDamageEffect
{
get
{
return Prefab.Body;
}
}
public StructurePrefab Prefab => prefab as StructurePrefab;
public HashSet<string> Tags
{
get { return prefab; }
}
protected Color spriteColor;
[Editable, Serialize("1.0,1.0,1.0,1.0", true)]
public Color SpriteColor
{
get { return spriteColor; }
set { spriteColor = value; }
}
public override Rectangle Rect
{
get
{
return base.Rect;
}
set
{
Rectangle oldRect = Rect;
base.Rect = value;
if (Prefab.Body) CreateSections();
else
{
foreach (WallSection sec in Sections)
{
Rectangle secRect = sec.rect;
secRect.X -= oldRect.X; secRect.Y -= oldRect.Y;
secRect.X *= value.Width; secRect.X /= oldRect.Width;
secRect.Y *= value.Height; secRect.Y /= oldRect.Height;
secRect.Width *= value.Width; secRect.Width /= oldRect.Width;
secRect.Height *= value.Height; secRect.Height /= oldRect.Height;
secRect.X += value.X; secRect.Y += value.Y;
sec.rect = secRect;
}
}
}
}
public float BodyWidth
{
get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth : rect.Width; }
}
public float BodyHeight
{
get { return Prefab.BodyHeight > 0.0f ? Prefab.BodyHeight : rect.Height; }
}
/// <summary>
/// In radians, takes flipping into account
/// </summary>
public float BodyRotation
{
get
{
float rotation = MathHelper.ToRadians(Prefab.BodyRotation);
if (FlippedX) rotation = -MathHelper.Pi - rotation;
if (FlippedY) rotation = -rotation;
return rotation;
}
}
/// <summary>
/// Offset of the physics body from the center of the structure. Takes flipping into account.
/// </summary>
public Vector2 BodyOffset
{
get
{
Vector2 bodyOffset = Prefab.BodyOffset;
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
return bodyOffset;
}
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
}
public override void Move(Vector2 amount)
{
base.Move(amount);
for (int i = 0; i < Sections.Length; i++)
{
Rectangle r = Sections[i].rect;
r.X += (int)amount.X;
r.Y += (int)amount.Y;
Sections[i].rect = r;
}
if (Bodies != null)
{
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
foreach (Body b in Bodies)
{
b.SetTransform(b.Position + simAmount, b.Rotation);
}
}
#if CLIENT
if (convexHulls!=null)
{
convexHulls.ForEach(x => x.Move(amount));
}
#endif
}
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine)
: base(sp, submarine)
{
if (rectangle.Width == 0 || rectangle.Height == 0) return;
System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
rect = rectangle;
#if CLIENT
TextureScale = sp.TextureScale;
#endif
spriteColor = prefab.SpriteColor;
if (ResizeHorizontal && !ResizeVertical)
{
IsHorizontal = true;
}
else if (ResizeVertical && !ResizeHorizontal)
{
IsHorizontal = false;
}
else
{
if (BodyWidth > 0.0f && BodyHeight > 0.0f)
{
IsHorizontal = BodyWidth > BodyHeight;
}
else
{
IsHorizontal = (rect.Width > rect.Height);
}
}
StairDirection = Prefab.StairDirection;
SerializableProperties = SerializableProperty.GetProperties(this);
if (Prefab.Body)
{
Bodies = new List<Body>();
WallList.Add(this);
CreateSections();
UpdateSections();
}
else
{
Sections = new WallSection[1];
Sections[0] = new WallSection(rect);
if (StairDirection != Direction.None)
{
CreateStairBodies();
}
}
InsertToList();
}
public override string ToString()
{
return Name;
}
public override MapEntity Clone()
{
var clone = new Structure(rect, Prefab, Submarine);
foreach (KeyValuePair<string, SerializableProperty> property in SerializableProperties)
{
if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
}
if (FlippedX) clone.FlipX(false);
if (FlippedY) clone.FlipY(false);
return clone;
}
private void CreateStairBodies()
{
Bodies = new List<Body>();
float bodyWidth = ConvertUnits.ToSimUnits(rect.Width * Math.Sqrt(2.0));
float bodyHeight = ConvertUnits.ToSimUnits(10);
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
bodyWidth, bodyHeight, 1.5f);
newBody.BodyType = BodyType.Static;
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + rect.Width / 2.0f);
/*stairPos += new Vector2(
(StairDirection == Direction.Right) ? -Submarine.GridSize.X * 1.5f : Submarine.GridSize.X * 1.5f,
-Submarine.GridSize.Y * 2.0f);*/
newBody.Rotation = (StairDirection == Direction.Right) ? MathHelper.PiOver4 : -MathHelper.PiOver4;
newBody.CollisionCategories = Physics.CollisionStairs;
newBody.Friction = 0.8f;
newBody.UserData = this;
newBody.Position = ConvertUnits.ToSimUnits(stairPos) + BodyOffset;
bodyDebugDimensions.Add(new Vector2(bodyWidth, bodyHeight));
Bodies.Add(newBody);
}
private void CreateSections()
{
int xsections = 1, ysections = 1;
int width = rect.Width, height = rect.Height;
if (!HasBody)
{
if (FlippedX && IsHorizontal)
{
xsections = (int)Math.Ceiling((float)rect.Width / prefab.sprite.SourceRect.Width);
width = prefab.sprite.SourceRect.Width;
}
else if (FlippedY && !IsHorizontal)
{
ysections = (int)Math.Ceiling((float)rect.Height / prefab.sprite.SourceRect.Height);
width = prefab.sprite.SourceRect.Height;
}
else
{
xsections = 1;
ysections = 1;
}
Sections = new WallSection[xsections];
}
else
{
if (IsHorizontal)
{
xsections = (int)Math.Ceiling((float)rect.Width / WallSectionSize);
Sections = new WallSection[xsections];
width = (int)WallSectionSize;
}
else
{
ysections = (int)Math.Ceiling((float)rect.Height / WallSectionSize);
Sections = new WallSection[ysections];
height = (int)WallSectionSize;
}
}
for (int x = 0; x < xsections; x++)
{
for (int y = 0; y < ysections; y++)
{
if (FlippedX || FlippedY)
{
Rectangle sectionRect = new Rectangle(
FlippedX ? rect.Right - (x + 1) * width : rect.X + x * width,
FlippedY ? rect.Y - rect.Height + (y + 1) * height : rect.Y - y * height,
width, height);
if (FlippedX)
{
int over = Math.Max(rect.X - sectionRect.X, 0);
sectionRect.X += over;
sectionRect.Width -= over;
}
else
{
sectionRect.Width -= (int)Math.Max(sectionRect.Right - rect.Right, 0.0f);
}
if (FlippedY)
{
int over = Math.Max(sectionRect.Y - rect.Y, 0);
sectionRect.Y -= over;
sectionRect.Height -= over;
}
else
{
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
}
//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);
}
else
{
Rectangle sectionRect = new Rectangle(rect.X + x * width, rect.Y - y * height, width, height);
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);
}
}
}
}
private Rectangle GenerateMergedRect(List<WallSection> mergedSections)
{
if (IsHorizontal)
return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
mergedSections.Sum(x => x.rect.Width), mergedSections.First().rect.Height);
else
{
return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
mergedSections.First().rect.Width, mergedSections.Sum(x => x.rect.Height));
}
}
private static Vector2[] CalculateExtremes(Rectangle sectionRect)
{
Vector2[] corners = new Vector2[4];
corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height);
corners[1] = new Vector2(sectionRect.X, sectionRect.Y);
corners[2] = new Vector2(sectionRect.Right, sectionRect.Y);
corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height);
return corners;
}
/// <summary>
/// Checks if there's a structure items can be attached to at the given position and returns it.
/// </summary>
public static Structure GetAttachTarget(Vector2 worldPosition)
{
foreach (MapEntity mapEntity in mapEntityList)
{
if (!(mapEntity is Structure structure)) continue;
if (!structure.Prefab.AllowAttachItems) continue;
if (structure.Bodies != null && structure.Bodies.Count > 0) continue;
Rectangle worldRect = mapEntity.WorldRect;
if (worldPosition.X < worldRect.X || worldPosition.X > worldRect.Right) continue;
if (worldPosition.Y > worldRect.Y || worldPosition.Y < worldRect.Y - worldRect.Height) continue;
return structure;
}
return null;
}
public override bool IsMouseOn(Vector2 position)
{
if (!base.IsMouseOn(position)) { return false; }
if (StairDirection == Direction.None)
{
Vector2 rectSize = rect.Size.ToVector2();
if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; }
if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; }
Vector2 bodyPos = WorldPosition + BodyOffset;
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, MathHelper.ToDegrees(BodyRotation));
return
Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
Math.Abs(transformedMousePos.Y - bodyPos.Y) < rectSize.Y / 2.0f;
}
else
{
if (StairDirection == Direction.Left)
{
return MathUtils.LineToPointDistance(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), position) < 40.0f;
}
else
{
return MathUtils.LineToPointDistance(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), position) < 40.0f;
}
}
}
public override void ShallowRemove()
{
base.ShallowRemove();
if (WallList.Contains(this)) WallList.Remove(this);
if (Bodies != null)
{
foreach (Body b in Bodies)
GameMain.World.RemoveBody(b);
}
if (Sections != null)
{
foreach (WallSection s in Sections)
{
if (s.gap != null)
{
s.gap.Remove();
s.gap = null;
}
}
}
#if CLIENT
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
#endif
}
public override void Remove()
{
base.Remove();
if (WallList.Contains(this)) WallList.Remove(this);
if (Bodies != null)
{
foreach (Body b in Bodies)
GameMain.World.RemoveBody(b);
}
if (Sections != null)
{
foreach (WallSection s in Sections)
{
if (s.gap != null)
{
s.gap.Remove();
s.gap = null;
}
}
}
#if CLIENT
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
#endif
}
private bool OnWallCollision(Fixture f1, Fixture f2, Contact contact)
{
if (Prefab.Platform)
{
if (f2.Body.UserData is Limb limb)
{
if (limb.character.AnimController.IgnorePlatforms) return false;
}
}
if (f2.Body.UserData is Limb)
{
var character = ((Limb)f2.Body.UserData).character;
if (character.DisableImpactDamageTimer > 0.0f || ((Limb)f2.Body.UserData).Mass < 100.0f) return true;
}
if (!Prefab.Platform && Prefab.StairDirection == Direction.None)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
int section = FindSectionIndex(pos);
if (section > -1)
{
Vector2 normal = contact.Manifold.LocalNormal;
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal) * f2.Body.Mass * 0.1f;
if (impact < 10.0f) return true;
#if CLIENT
SoundPlayer.PlayDamageSound("StructureBlunt", impact, SectionPosition(section, true), tags: Tags);
#endif
AddDamage(section, impact);
}
}
return true;
}
public WallSection GetSection(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return null;
return Sections[sectionIndex];
}
public bool SectionBodyDisabled(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false;
return (Sections[sectionIndex].damage >= Prefab.Health);
}
/// <summary>
/// Sections that are leaking have a gap placed on them
/// </summary>
public bool SectionIsLeaking(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false;
return (Sections[sectionIndex].damage >= Prefab.Health * LeakThreshold);
}
public int SectionLength(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return 0;
return (IsHorizontal ? Sections[sectionIndex].rect.Width : Sections[sectionIndex].rect.Height);
}
public void AddDamage(int sectionIndex, float damage, Character attacker = null)
{
if (!Prefab.Body || Prefab.Platform || Indestructible) return;
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) return;
var section = Sections[sectionIndex];
#if CLIENT
float particleAmount = Math.Min(Health - section.damage, damage) * Rand.Range(0.01f, 1.0f);
particleAmount = Math.Min(particleAmount + Rand.Range(-5, 1), 5);
for (int i = 0; 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;
}
#endif
#if CLIENT
if (GameMain.Client == null)
{
#endif
SetDamage(sectionIndex, section.damage + damage, attacker);
#if CLIENT
}
#endif
}
public int FindSectionIndex(Vector2 displayPos, bool world = false, bool clamp = false)
{
if (!Sections.Any()) return -1;
if (world && Submarine != null)
{
displayPos -= Submarine.Position;
}
//if the sub has been flipped horizontally, the first section may be smaller than wallSectionSize
//and we need to adjust the position accordingly
if (Sections[0].rect.Width < WallSectionSize)
{
displayPos.X += WallSectionSize - Sections[0].rect.Width;
}
int index = (IsHorizontal) ?
(int)Math.Floor((displayPos.X - rect.X) / WallSectionSize) :
(int)Math.Floor((rect.Y - displayPos.Y) / WallSectionSize);
if (clamp)
{
index = MathHelper.Clamp(index, 0, Sections.Length - 1);
}
else if (index < 0 || index > Sections.Length - 1)
{
return -1;
}
return index;
}
public float SectionDamage(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return 0.0f;
return Sections[sectionIndex].damage;
}
public Vector2 SectionPosition(int sectionIndex, bool world = false)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return Vector2.Zero;
if (Prefab.BodyRotation == 0.0f)
{
Vector2 sectionPos = new Vector2(
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
Sections[sectionIndex].rect.Y - Sections[sectionIndex].rect.Height / 2.0f);
if (world && Submarine != null) sectionPos += Submarine.Position;
return sectionPos;
}
else
{
Rectangle sectionRect = Sections[sectionIndex].rect;
float diffFromCenter;
if (IsHorizontal)
{
diffFromCenter = (sectionRect.Center.X - rect.Center.X) / (float)rect.Width * BodyWidth;
}
else
{
diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight;
}
if (FlippedX) diffFromCenter = -diffFromCenter;
Vector2 sectionPos = Position + new Vector2(
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
if (world && Submarine != null) sectionPos += Submarine.Position;
return sectionPos;
}
}
partial void AdjustKarma(IDamageable attacker, float amount);
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);
Vector2 transformedPos = worldPosition;
if (Submarine != null) transformedPos -= Submarine.Position;
float damageAmount = 0.0f;
for (int i = 0; i < SectionCount; i++)
{
Rectangle sectionRect = Sections[i].rect;
sectionRect.Y -= Sections[i].rect.Height;
if (MathUtils.CircleIntersectsRectangle(transformedPos, attack.DamageRange, sectionRect))
{
damageAmount = attack.GetStructureDamage(deltaTime);
AddDamage(i, damageAmount, attacker);
#if CLIENT
GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f);
#endif
}
}
#if CLIENT
if (playSound)
{
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)
{
if (Submarine != null && Submarine.GodMode || Indestructible) return;
if (!Prefab.Body) return;
if (!MathUtils.IsValid(damage)) return;
damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health);
#if SERVER
if (GameMain.Server != null && createNetworkEvent && damage != Sections[sectionIndex].damage)
{
GameMain.Server.CreateEntityEvent(this);
}
bool noGaps = true;
for (int i = 0; i < Sections.Length; i++)
{
if (i != sectionIndex && SectionIsLeaking(i))
{
noGaps = false;
break;
}
}
#endif
if (damage < Prefab.Health * LeakThreshold)
{
if (Sections[sectionIndex].gap != null)
{
#if SERVER
//the structure doesn't have any other gap, log the structure being fixed
if (noGaps && attacker != null)
{
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
DebugConsole.Log("Removing gap (ID " + Sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") from wall " + ID);
//remove existing gap if damage is below leak threshold
Sections[sectionIndex].gap.Open = 0.0f;
Sections[sectionIndex].gap.Remove();
Sections[sectionIndex].gap = null;
}
}
else
{
if (Sections[sectionIndex].gap == null)
{
Rectangle gapRect = Sections[sectionIndex].rect;
float diffFromCenter;
if (IsHorizontal)
{
diffFromCenter = (gapRect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth;
if (BodyWidth > 0.0f) gapRect.Width = (int)(BodyWidth * (gapRect.Width / (float)this.rect.Width));
if (BodyHeight > 0.0f) gapRect.Height = (int)BodyHeight;
}
else
{
diffFromCenter = ((gapRect.Y - gapRect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight;
if (BodyWidth > 0.0f) gapRect.Width = (int)BodyWidth;
if (BodyHeight > 0.0f) gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height));
}
if (FlippedX) diffFromCenter = -diffFromCenter;
if (BodyRotation != 0.0f)
{
Vector2 structureCenter = Position;
Vector2 gapPos = structureCenter + new Vector2(
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
gapRect = new Rectangle((int)(gapPos.X - gapRect.Width / 2), (int)(gapPos.Y + gapRect.Height / 2), gapRect.Width, gapRect.Height);
}
gapRect.X -= 10;
gapRect.Y += 10;
gapRect.Width += 20;
gapRect.Height += 20;
bool horizontalGap = !IsHorizontal;
if (Prefab.BodyRotation != 0.0f)
{
//rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80)
float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2;
//diagonal if 30 < angle < 60
bool diagonal = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
//gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical
if (diagonal)
{
horizontalGap = gapRect.Y - gapRect.Height / 2 < Position.Y;
}
}
Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine);
//free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and
//that clients create them in the correct order along with every other entity created/removed during the round
//which COULD be done via entityspawner, but it's unnecessary because we never access these gaps by ID
Sections[sectionIndex].gap.FreeID();
Sections[sectionIndex].gap.ShouldBeSaved = false;
Sections[sectionIndex].gap.ConnectedWall = this;
DebugConsole.Log("Created gap (ID " + Sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") on wall " + ID);
//AdjustKarma(attacker, 300);
#if SERVER
//the structure didn't have any other gaps yet, log the breach
if (noGaps && attacker != null)
{
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + attacker.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
}
float gapOpen = (damage / Prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
Sections[sectionIndex].gap.Open = gapOpen;
}
float damageDiff = damage - Sections[sectionIndex].damage;
bool hadHole = SectionBodyDisabled(sectionIndex);
Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health);
//otherwise it's possible to infinitely gain karma by welding fixed things
if (attacker != null && damageDiff != 0.0f)
{
AdjustKarma(attacker, damageDiff);
#if CLIENT
if (GameMain.Client == null)
{
#endif
if (damageDiff < 0.0f)
{
attacker.Info.IncreaseSkillLevel("mechanical",
-damageDiff * SkillIncreaseMultiplier / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f),
SectionPosition(sectionIndex, true));
}
#if CLIENT
}
#endif
}
bool hasHole = SectionBodyDisabled(sectionIndex);
if (hadHole == hasHole) return;
UpdateSections();
}
public void SetCollisionCategory(Category collisionCategory)
{
if (Bodies == null) return;
foreach (Body body in Bodies)
{
body.CollisionCategories = collisionCategory;
}
}
private void UpdateSections()
{
foreach (Body b in Bodies)
{
GameMain.World.RemoveBody(b);
}
Bodies.Clear();
bodyDebugDimensions.Clear();
#if CLIENT
convexHulls?.ForEach(ch => ch.Remove());
convexHulls?.Clear();
#endif
bool hasHoles = false;
var mergedSections = new List<WallSection>();
for (int i = 0; i < Sections.Length; i++ )
{
// if there is a gap and we have sections to merge, do it.
if (SectionBodyDisabled(i))
{
hasHoles = true;
if (!mergedSections.Any()) continue;
var mergedRect = GenerateMergedRect(mergedSections);
mergedSections.Clear();
CreateRectBody(mergedRect, createConvexHull: true);
}
else
{
mergedSections.Add(Sections[i]);
}
}
// take care of any leftover pieces
if (mergedSections.Count > 0)
{
var mergedRect = GenerateMergedRect(mergedSections);
CreateRectBody(mergedRect, createConvexHull: true);
}
//if the section has holes (or is just one big hole with no bodies),
//we need a sensor for repairtools to be able to target the structure
if (hasHoles || !Bodies.Any())
{
Body sensorBody = CreateRectBody(rect, createConvexHull: false);
sensorBody.CollisionCategories = Physics.CollisionRepair;
sensorBody.IsSensor = true;
}
}
private Body CreateRectBody(Rectangle rect, bool createConvexHull)
{
float diffFromCenter;
if (IsHorizontal)
{
diffFromCenter = (rect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth;
if (BodyWidth > 0.0f) rect.Width = Math.Max((int)Math.Round(BodyWidth * (rect.Width / (float)this.rect.Width)), 1);
if (BodyHeight > 0.0f) rect.Height = (int)BodyHeight;
}
else
{
diffFromCenter = ((rect.Y - rect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight;
if (BodyWidth > 0.0f) rect.Width = (int)BodyWidth;
if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1);
}
if (FlippedX) diffFromCenter = -diffFromCenter;
Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset);
if (FlippedX) { bodyOffset.X = -bodyOffset.X; }
if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
ConvertUnits.ToSimUnits(rect.Width),
ConvertUnits.ToSimUnits(rect.Height),
1.5f);
newBody.BodyType = BodyType.Static;
//newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
newBody.Friction = 0.5f;
newBody.OnCollision += OnWallCollision;
newBody.CollisionCategories = (Prefab.Platform) ? Physics.CollisionPlatform : Physics.CollisionWall;
newBody.UserData = this;
Vector2 structureCenter = ConvertUnits.ToSimUnits(Position);
if (BodyRotation != 0.0f)
{
newBody.Position = structureCenter + bodyOffset + new Vector2(
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * ConvertUnits.ToSimUnits(diffFromCenter);
newBody.Rotation = -BodyRotation;
}
else
{
newBody.Position = structureCenter + (IsHorizontal ? Vector2.UnitX : Vector2.UnitY) * ConvertUnits.ToSimUnits(diffFromCenter) + bodyOffset;
}
if (createConvexHull)
{
CreateConvexHull(ConvertUnits.ToDisplayUnits(newBody.Position), rect.Size.ToVector2(), newBody.Rotation);
}
Bodies.Add(newBody);
bodyDebugDimensions.Add(new Vector2(ConvertUnits.ToSimUnits(rect.Width), ConvertUnits.ToSimUnits(rect.Height)));
return newBody;
}
partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation);
public override void FlipX(bool relativeToSub)
{
base.FlipX(relativeToSub);
if (Prefab.CanSpriteFlipX)
{
SpriteEffects ^= SpriteEffects.FlipHorizontally;
}
if (StairDirection != Direction.None)
{
StairDirection = StairDirection == Direction.Left ? Direction.Right : Direction.Left;
Bodies.ForEach(b => GameMain.World.RemoveBody(b));
Bodies.Clear();
bodyDebugDimensions.Clear();
CreateStairBodies();
}
if (HasBody)
{
CreateSections();
UpdateSections();
}
}
public override void FlipY(bool relativeToSub)
{
base.FlipY(relativeToSub);
if (Prefab.CanSpriteFlipY)
{
SpriteEffects ^= SpriteEffects.FlipVertically;
}
if (StairDirection != Direction.None)
{
StairDirection = StairDirection == Direction.Left ? Direction.Right : Direction.Left;
Bodies.ForEach(b => GameMain.World.RemoveBody(b));
Bodies.Clear();
bodyDebugDimensions.Clear();
CreateStairBodies();
}
if (HasBody)
{
CreateSections();
UpdateSections();
}
}
public static Structure Load(XElement element, Submarine submarine)
{
string name = element.Attribute("name").Value;
string identifier = element.GetAttributeString("identifier", "");
StructurePrefab prefab = FindPrefab(name, identifier);
if (prefab == null)
{
DebugConsole.ThrowError("Error loading structure - structure prefab \"" + name + "\" (identifier \"" + identifier + "\") not found.");
return null;
}
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
Structure s = new Structure(rect, prefab, submarine)
{
Submarine = submarine,
ID = (ushort)int.Parse(element.Attribute("ID").Value)
};
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "section":
int index = subElement.GetAttributeInt("i", -1);
if (index == -1) continue;
s.Sections[index].damage = subElement.GetAttributeFloat("damage", 0.0f);
break;
}
}
if (element.GetAttributeBool("flippedx", false)) s.FlipX(false);
if (element.GetAttributeBool("flippedy", false)) s.FlipY(false);
SerializableProperty.DeserializeProperties(s, element);
return s;
}
public static StructurePrefab FindPrefab(string name, string identifier)
{
StructurePrefab prefab = null;
if (string.IsNullOrEmpty(identifier))
{
//legacy support:
//1. attempt to find a prefab with an empty identifier and a matching name
prefab = MapEntityPrefab.Find(name, "") as StructurePrefab;
//2. not found, attempt to find a prefab with a matching name
if (prefab == null) prefab = MapEntityPrefab.Find(name) as StructurePrefab;
//3. not found, attempt to find a prefab that uses the previous name as an identifier
if (prefab == null) prefab = MapEntityPrefab.Find(null, name) as StructurePrefab;
}
else
{
prefab = MapEntityPrefab.Find(null, identifier) as StructurePrefab;
}
return prefab;
}
public override XElement Save(XElement parentElement)
{
XElement element = new XElement("Structure");
element.Add(
new XAttribute("name", prefab.Name),
new XAttribute("identifier", prefab.Identifier),
new XAttribute("ID", ID),
new XAttribute("rect",
(int)(rect.X - Submarine.HiddenSubPosition.X) + "," +
(int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," +
rect.Width + "," + rect.Height));
if (FlippedX) element.Add(new XAttribute("flippedx", true));
if (FlippedY) element.Add(new XAttribute("flippedy", true));
for (int i = 0; i < Sections.Length; i++)
{
if (Sections[i].damage == 0.0f) continue;
var sectionElement =
new XElement("section",
new XAttribute("i", i),
new XAttribute("damage", Sections[i].damage));
element.Add(sectionElement);
}
SerializableProperty.SerializeProperties(this, element);
parentElement.Add(element);
return element;
}
public override void OnMapLoaded()
{
for (int i = 0; i < Sections.Length; i++)
{
SetDamage(i, Sections[i].damage, createNetworkEvent: false);
}
}
}
}