4 using FarseerPhysics.Dynamics;
5 using FarseerPhysics.Dynamics.Contacts;
6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
10 using System.Xml.Linq;
11 using System.Collections.Immutable;
14 using System.Diagnostics;
15 using Microsoft.Xna.Framework.Graphics;
41 System.Diagnostics.Debug.Assert(
rect.Width > 0 &&
rect.Height > 0);
51 public static List<Structure>
WallList =
new List<Structure>();
53 const float LeakThreshold = 0.1f;
54 const float BigGapThreshold = 0.7f;
61 private readonly Dictionary<Body, Vector2> bodyDimensions =
new Dictionary<Body, Vector2>();
63 private static Explosion explosionOnBroken;
81 get {
return base.Prefab.
Sprite; }
86 get {
return Prefab.Platform; }
95 public override string Name
97 get {
return base.Prefab.Name.Value; }
102 get {
return Prefab.Body; }
105 public List<Body>
Bodies {
get;
private set; }
121 private float? maxHealth;
126 get => maxHealth ??
Prefab.Health;
127 set => maxHealth = value;
130 private float crushDepth;
145 return base.DrawBelowWater ||
Prefab.BackgroundSprite !=
null;
208 private float scale = 1.0f;
211 get {
return scale; }
214 if (scale == value) {
return; }
215 scale = MathHelper.Clamp(value, 0.1f, 10.0f);
217 float relativeScale = scale / base.Prefab.Scale;
221 int newWidth = Math.Max(
ResizeHorizontal ?
rect.Width : (
int)(defaultRect.Width * relativeScale), 1);
222 int newHeight = Math.Max(
ResizeVertical ?
rect.Height : (
int)(defaultRect.Height * relativeScale), 1);
250 rotationRad = MathHelper.WrapAngle(MathHelper.ToRadians(value));
272 MathHelper.Clamp(value.X, 0.01f, 10),
273 MathHelper.Clamp(value.Y, 0.01f, 10));
300 SetLightTextureOffset();
312 get {
return defaultRect; }
313 set { defaultRect = value; }
337 secRect.X -= oldRect.X; secRect.Y -= oldRect.Y;
338 secRect.X *= value.Width; secRect.X /= oldRect.Width;
339 secRect.Y *= value.Height; secRect.Y /= oldRect.Height;
340 secRect.Width *= value.Width; secRect.Width /= oldRect.Width;
341 secRect.Height *= value.Height; secRect.Height /= oldRect.Height;
342 secRect.X += value.X; secRect.Y += value.Y;
351 get {
return Prefab.BodyWidth > 0.0f ?
Prefab.BodyWidth * scale :
rect.Width; }
355 get {
return Prefab.BodyHeight > 0.0f ?
Prefab.BodyHeight * scale :
rect.Height; }
368 if (
FlippedX) { rotation = -MathHelper.Pi - rotation; }
369 if (
FlippedY) { rotation = -rotation; }
373 if (
FlippedX) { rotation = -rotation; }
374 if (
FlippedY) { rotation = -MathHelper.Pi -rotation; }
376 rotation = MathHelper.WrapAngle(rotation);
387 Vector2 bodyOffset =
Prefab.BodyOffset;
390 bodyOffset = MathUtils.RotatePoint(bodyOffset, -
rotationRad);
392 if (
FlippedX) { bodyOffset.X = -bodyOffset.X; }
393 if (
FlippedY) { bodyOffset.Y = -bodyOffset.Y; }
411 public override void Move(Vector2 amount,
bool ignoreContacts =
true)
413 if (!MathUtils.IsValid(amount))
415 DebugConsole.ThrowError($
"Attempted to move a structure by an invalid amount ({amount})\n{Environment.StackTrace.CleanupStackTrace()}");
419 base.Move(amount, ignoreContacts);
421 for (
int i = 0; i <
Sections.Length; i++)
424 r.X += (int)amount.X;
425 r.Y += (
int)amount.Y;
431 Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
432 foreach (Body b
in Bodies)
434 Vector2 pos = b.Position + simAmount;
437 b.SetTransformIgnoreContacts(ref pos, b.Rotation);
441 b.SetTransform(pos, b.Rotation);
447 convexHulls?.ForEach(x => x.Move(amount));
458 : base(sp, submarine, id)
460 System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
461 if (rectangle.Width == 0 || rectangle.Height == 0) {
return; }
462 defaultRect = rectangle;
498 if (element?.GetAttribute(nameof(
CastShadow)) ==
null)
505 Bodies =
new List<Body>();
521 foreach (var subElement
in sp.ConfigElement.Elements())
523 if (subElement.Name.ToString().Equals(
"light", StringComparison.OrdinalIgnoreCase))
525 Vector2 pos =
rect.Location.ToVector2();
526 pos.Y +=
rect.Height;
532 IsBackground =
false,
533 Color = subElement.GetAttributeColor(
"lightcolor", Color.White),
534 SpriteScale = Vector2.One,
536 LightTextureTargetSize =
rect.Size.ToVector2(),
540 Flicker = subElement.GetAttributeFloat(
"flicker", 0f),
541 FlickerSpeed = subElement.GetAttributeFloat(
"flickerspeed", 0f),
542 PulseAmount = subElement.GetAttributeFloat(
"pulseamount", 0f),
543 PulseFrequency = subElement.GetAttributeFloat(
"pulsefrequency", 0f),
544 BlinkFrequency = subElement.GetAttributeFloat(
"blinkfrequency", 0f)
550 SetLightTextureOffset();
560 MinSightRange = 1000,
561 MaxSightRange = 4000,
568 DebugConsole.Log(
"Created " +
Name +
" (" +
ID +
")");
571 partial
void InitProjSpecific();
582 defaultRect = defaultRect
586 if (!property.Value.Attributes.OfType<
Editable>().Any()) {
continue; }
587 clone.SerializableProperties[
property.Key].TrySetValue(clone, property.Value.GetValue(
this));
595 private void CreateStairBodies()
597 Bodies =
new List<Body>();
598 bodyDimensions.Clear();
600 float stairAngle = MathHelper.ToRadians(Math.Min(
Prefab.StairAngle, 75.0f));
602 float bodyWidth = ConvertUnits.ToSimUnits(
rect.Width / Math.Cos(stairAngle));
603 float bodyHeight = ConvertUnits.ToSimUnits(10);
605 float stairHeight =
rect.Width * (float)Math.Tan(stairAngle);
607 Body newBody =
GameMain.
World.CreateRectangle(bodyWidth, bodyHeight, 1.5f);
611 newBody.BodyType = BodyType.Static;
612 Vector2 stairRectHeightDiff =
new Vector2(0f, stairHeight / 2.0f -
rect.Height / 2.0f);
613 stairRectHeightDiff = MathUtils.RotatePoint(stairRectHeightDiff, -rotationWithFlip);
614 if (
FlippedY) { stairRectHeightDiff = -stairRectHeightDiff; }
615 Vector2 stairPos =
new Vector2(
Position.X,
rect.Y -
rect.Height / 2.0f) + stairRectHeightDiff;
617 newBody.CollisionCategories = Physics.CollisionStairs;
618 newBody.Friction = 0.8f;
619 newBody.UserData =
this;
621 newBody.Position = ConvertUnits.ToSimUnits(stairPos) +
BodyOffset *
Scale;
623 bodyDimensions.Add(newBody,
new Vector2(bodyWidth, bodyHeight));
628 private void CreateSections()
630 int xsections = 1, ysections = 1;
631 int width =
rect.Width, height =
rect.Height;
633 WallSection[] prevSections =
null;
642 xsections = (int)Math.Ceiling((
float)
rect.Width / base.Prefab.Sprite.SourceRect.Width);
643 width = base.Prefab.Sprite.SourceRect.Width;
647 ysections = (int)Math.Ceiling((
float)
rect.Height / base.Prefab.Sprite.SourceRect.Height);
648 width = base.Prefab.Sprite.SourceRect.Height;
655 Sections =
new WallSection[xsections];
663 Sections =
new WallSection[xsections];
669 Sections =
new WallSection[ysections];
674 for (
int x = 0; x < xsections; x++)
676 for (
int y = 0; y < ysections; y++)
687 int over = Math.Max(
rect.X - sectionRect.X, 0);
688 sectionRect.X += over;
689 sectionRect.Width -= over;
693 sectionRect.Width -= (int)Math.Max(sectionRect.Right -
rect.Right, 0.0f);
697 int over = Math.Max(sectionRect.Y -
rect.Y, 0);
698 sectionRect.Y -= over;
699 sectionRect.Height -= over;
703 sectionRect.Height -= (int)Math.Max((
rect.Y -
rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
709 Sections[xIndex + yIndex] =
new WallSection(sectionRect,
this);
714 sectionRect.Width -= (int)Math.Max(sectionRect.Right -
rect.Right, 0.0f);
715 sectionRect.Height -= (int)Math.Max((
rect.Y -
rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
717 Sections[x + y] =
new WallSection(sectionRect,
this);
722 if (prevSections !=
null &&
Sections.Length == prevSections.Length)
724 for (
int i = 0; i <
Sections.Length; i++)
731 private Rectangle GenerateMergedRect(List<WallSection> mergedSections)
734 return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
735 mergedSections.Sum(x => x.rect.Width), mergedSections.First().rect.Height);
738 return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
739 mergedSections.First().rect.Width, mergedSections.Sum(x => x.rect.Height));
744 => Quad2D.FromSubmarineRectangle(
rect).Rotated(
756 if (!(mapEntity is
Structure structure)) {
continue; }
757 if (!structure.Prefab.AllowAttachItems) {
continue; }
758 if (structure.Bodies !=
null && structure.Bodies.Count > 0) {
continue; }
760 if (worldPosition.X < worldRect.X || worldPosition.X > worldRect.Right) {
continue; }
761 if (worldPosition.Y > worldRect.Y || worldPosition.Y < worldRect.Y - worldRect.Height) {
continue; }
771 Vector2 rectSize =
rect.Size.ToVector2();
777 Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos,
BodyRotation);
780 Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
781 Math.Abs(transformedMousePos.Y - bodyPos.Y) < rectSize.Y / 2.0f;
785 Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(
804 base.ShallowRemove();
810 foreach (Body b
in Bodies)
830 if (convexHulls !=
null) convexHulls.ForEach(x => x.Remove());
846 foreach (Body b
in Bodies)
866 if (convexHulls !=
null) convexHulls.ForEach(x => x.Remove());
874 private bool OnWallCollision(Fixture f1, Fixture f2, Contact contact)
878 if (f2.Body.UserData is
Limb limb)
880 if (limb.character.AnimController.IgnorePlatforms)
return false;
884 if (f2.Body.UserData is Limb)
886 var character = ((Limb)f2.Body.UserData).character;
887 if (character.DisableImpactDamageTimer > 0.0f || ((Limb)f2.Body.UserData).Mass < 100.0f)
return true;
890 OnImpactProjSpecific(f1, f2, contact);
895 partial
void OnImpactProjSpecific(Fixture f1, Fixture f2, Contact contact);
899 if (sectionIndex < 0 || sectionIndex >=
Sections.Length) {
return null; }
906 if (sectionIndex < 0 || sectionIndex >=
Sections.Length) {
return false; }
912 for (
int i = 0; i <
Sections.Length; i++)
924 if (sectionIndex < 0 || sectionIndex >=
Sections.Length) {
return false; }
930 if (sectionIndex < 0 || sectionIndex >=
Sections.Length) {
return false; }
936 if (sectionIndex < 0 || sectionIndex >=
Sections.Length)
return 0;
941 public override bool AddUpgrade(Upgrade upgrade,
bool createNetworkEvent =
false)
943 if (!upgrade.Prefab.IsWallUpgrade) {
return false; }
945 Upgrade existingUpgrade =
GetUpgrade(upgrade.Identifier);
947 if (existingUpgrade !=
null)
949 existingUpgrade.Level += upgrade.Level;
950 existingUpgrade.ApplyUpgrade();
956 upgrade.ApplyUpgrade();
964 public void AddDamage(
int sectionIndex,
float damage,
Character attacker =
null,
bool emitParticles =
true,
bool createWallDamageProjectiles =
false)
968 if (sectionIndex < 0 || sectionIndex >
Sections.Length - 1) {
return; }
970 var section =
Sections[sectionIndex];
971 float prevDamage = section.
damage;
974 SetDamage(sectionIndex, section.damage + damage, attacker, createWallDamageProjectiles: createWallDamageProjectiles);
977 if (damage > 0 && emitParticles)
979 float dmg = Math.Min(section.damage - prevDamage, damage);
980 float particleAmount = MathHelper.Lerp(0, 25, MathUtils.InverseLerp(0, 100, dmg * Rand.Range(0.75f, 1.25f)));
982 if (particleAmount < 1 && Rand.Value() < 0.10f)
986 for (
int i = 1; i <= particleAmount; i++)
988 var worldRect = section.WorldRect;
989 var directionUnitX = MathUtils.RotatedUnitXRadians(
BodyRotation);
990 var directionUnitY = directionUnitX.YX().FlipX();
991 Vector2 particlePos =
new Vector2(
992 Rand.Range(0, worldRect.Width + 1),
993 Rand.Range(-worldRect.Height, 1));
994 particlePos -= worldRect.Size.ToVector2().FlipY() * 0.5f;
997 particlePosFinal += particlePos.X * directionUnitX + particlePos.Y * directionUnitY;
1000 position: particlePosFinal,
1001 velocity: Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
1002 if (particle ==
null) {
break; }
1010 if (
Sections.None()) {
return -1; }
1039 index = MathHelper.Clamp(index, 0,
Sections.Length - 1);
1041 else if (index < 0 || index >
Sections.Length - 1)
1050 if (sectionIndex < 0 || sectionIndex >=
Sections.Length)
return 0.0f;
1062 return MathUtils.RotatedUnitXRadians(rotation);
1068 if (sectionIndex < 0 || sectionIndex >=
Sections.Length)
1070 return Vector2.Zero;
1075 Vector2 sectionPos =
new Vector2(
1088 float diffFromCenter;
1091 diffFromCenter = (sectionRect.Center.X -
rect.Center.X) / (
float)
rect.Width *
BodyWidth;
1095 diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (
rect.Y -
rect.Height / 2)) / (
float)
rect.Height *
BodyHeight;
1096 diffFromCenter = -diffFromCenter;
1114 Vector2 transformedPos = worldPosition;
1119 var center =
Rect.Location.ToVector2() +
Rect.Size.ToVector2().FlipY() * 0.5f;
1123 transformedPos = MathUtils.RotatePointAroundTarget(transformedPos, center, rotation);
1126 float damageAmount = 0.0f;
1131 if (MathUtils.CircleIntersectsRectangle(transformedPos, attack.
DamageRange, sectionRect))
1144 if (playSound && damageAmount > 0)
1146 string damageSound =
Prefab.DamageSound;
1147 if (
string.IsNullOrWhiteSpace(damageSound))
1151 SoundPlayer.PlayDamageSound(damageSound, damageAmount, worldPosition, tags:
Tags);
1155 if (
Submarine !=
null && damageAmount > 0 && attacker !=
null)
1168 bool createNetworkEvent =
true,
1169 bool isNetworkEvent =
true,
1170 bool createExplosionEffect =
true,
1171 bool createWallDamageProjectiles =
false)
1174 if (!
Prefab.Body) {
return; }
1175 if (!MathUtils.IsValid(damage)) {
return; }
1177 damage = MathHelper.Clamp(damage, 0.0f,
MaxHealth -
Prefab.MinHealth);
1179 if (
Sections[sectionIndex].NoPhysicsBody) {
return; }
1184 GameMain.Server.CreateEntityEvent(
this);
1187 for (
int i = 0; i <
Sections.Length; i++)
1199 if (
Sections[sectionIndex].gap !=
null)
1203 if (noGaps && attacker !=
null)
1208 DebugConsole.Log(
"Removing gap (ID " +
Sections[sectionIndex].gap.
ID +
", section: " + sectionIndex +
") from wall " +
ID);
1219 if (
Sections[sectionIndex].gap ==
null)
1222 float diffFromCenter;
1225 diffFromCenter = (gapRect.Center.X - this.rect.Center.X) / (
float)this.rect.Width *
BodyWidth;
1226 if (
BodyWidth > 0.0f) { gapRect.Width = (int)(
BodyWidth * (gapRect.Width / (
float)this.rect.Width)); }
1232 if (
FlippedX) { diffFromCenter = -diffFromCenter; }
1236 diffFromCenter = ((gapRect.Y - gapRect.Height / 2) - (this.rect.Y -
this.rect.Height / 2)) / (
float)this.rect.Height *
BodyHeight;
1242 if (
BodyHeight > 0.0f) { gapRect.Height = (int)(
BodyHeight * (gapRect.Height / (
float)this.rect.Height)); }
1243 if (
FlippedY) { diffFromCenter = -diffFromCenter; }
1248 Vector2 structureCenter =
Position;
1249 Vector2 gapPos = structureCenter +
new Vector2(
1252 gapRect =
new Rectangle((
int)(gapPos.X - gapRect.Width / 2), (
int)(gapPos.Y + gapRect.Height / 2), gapRect.Width, gapRect.Height);
1257 gapRect.Width += 20;
1258 gapRect.Height += 20;
1260 bool rotatedEnoughToChangeOrientation = (MathUtils.WrapAngleTwoPi(
rotationRad - MathHelper.PiOver4) % MathHelper.Pi < MathHelper.PiOver2);
1261 if (rotatedEnoughToChangeOrientation)
1263 var center = gapRect.Location + gapRect.Size.FlipY() /
new Point(2);
1264 var topLeft = gapRect.Location;
1265 var diff = topLeft - center;
1266 diff = diff.FlipY().YX().FlipY();
1267 var newTopLeft = diff + center;
1268 gapRect =
new Rectangle(newTopLeft, gapRect.Size.YX());
1270 bool horizontalGap = rotatedEnoughToChangeOrientation
1273 bool diagonalGap =
false;
1277 float sectorizedRotation = MathUtils.WrapAngleTwoPi(
BodyRotation) % MathHelper.PiOver2;
1279 diagonalGap = sectorizedRotation is > MathHelper.Pi / 6 and < MathHelper.Pi / 3;
1283 horizontalGap = gapRect.Y - gapRect.Height / 2 <
Position.Y;
1284 if (
FlippedY) { horizontalGap = !horizontalGap; }
1296 DebugConsole.Log(
"Created gap (ID " +
Sections[sectionIndex].gap.
ID +
", section: " + sectionIndex +
") on wall " +
ID);
1301 if (noGaps && attacker !=
null)
1311 if (damageRatio > BigGapThreshold)
1313 gapOpen = MathHelper.Lerp(0.35f, 0.75f, MathUtils.InverseLerp(BigGapThreshold, 1.0f, damageRatio));
1315 else if (damageRatio > LeakThreshold)
1317 gapOpen = MathHelper.Lerp(0f, 0.35f, MathUtils.InverseLerp(LeakThreshold, BigGapThreshold, damageRatio));
1322 if (gapOpen - prevGapOpenState > 0.25f && createExplosionEffect && !gap.IsRoomToRoom)
1324 CreateWallDamageExplosion(gap, attacker, createWallDamageProjectiles);
1333 if (attacker !=
null && damageDiff != 0.0f)
1336 OnHealthChangedProjSpecific(attacker, damageDiff);
1339 if (damageDiff < 0.0f)
1341 attacker.Info?.ApplySkillGain(
Barotrauma.Tags.MechanicalSkill,
1349 if (hadHole == hasHole) {
return; }
1354 private static void CreateWallDamageExplosion(
Gap gap,
Character attacker,
bool createProjectiles)
1356 const float explosionRange = 500.0f;
1357 float explosionStrength = gap.
Open;
1359 var linkedHull = gap.
linkedTo.FirstOrDefault() as
Hull;
1360 if (linkedHull !=
null)
1365 foreach (var otherGap
in linkedHull.ConnectedGaps)
1367 if (otherGap == gap || otherGap.IsRoomToRoom || otherGap.Open < 0.25f) {
continue; }
1368 explosionStrength -= Math.Max(0, explosionRange - Vector2.Distance(otherGap.WorldPosition, gap.
WorldPosition)) / explosionRange;
1369 if (explosionStrength <= 0.0f) {
return; }
1373 if (explosionOnBroken ==
null)
1375 explosionOnBroken =
new Explosion(explosionRange, force: 5.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f);
1376 if (AfflictionPrefab.Prefabs.TryGet(
"lacerations".ToIdentifier(), out AfflictionPrefab lacerations))
1382 explosionOnBroken.
Attack.
Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(5.0f),
null);
1395 explosionOnBroken.
Attack.
Stun = MathHelper.Clamp(explosionStrength, 0.5f, 1.0f);
1397 if (attacker?.AIController is EnemyAIController) { explosionOnBroken.
IgnoredCharacters.Add(attacker); }
1400 if (createProjectiles)
1402 if (ItemPrefab.Prefabs.TryGet(
"walldamageprojectile", out var projectilePrefab) && linkedHull !=
null)
1405 (linkedHull.WorldPosition.X < gap.
WorldPosition.X ? MathHelper.Pi : 0) :
1406 (linkedHull.WorldPosition.Y < gap.
WorldPosition.Y ? -MathHelper.PiOver2 : MathHelper.PiOver2);
1409 item.body.SetTransformIgnoreContacts(item.body.SimPosition, angle);
1410 var projectile = item.GetComponent<Items.Components.Projectile>();
1418 if (linkedHull !=
null)
1420 for (
int i = 0; i <= 50; i++)
1426 emitDirection =
new Vector2(emitDirection.X + Rand.Range(-0.2f, 0.2f), emitDirection.Y + Rand.Range(-0.2f, 0.2f));
1427 var shrapnelParticle = GameMain.ParticleManager.CreateParticle(
"shrapnel", particlePos, emitDirection * Rand.Range(100.0f, 3000.0f), hullGuess: linkedHull, collisionIgnoreTimer: 0.1f);
1428 var sparkParticle = GameMain.ParticleManager.CreateParticle(
"whitespark", particlePos, emitDirection * Rand.Range(1000.0f, 3000.0f), hullGuess: linkedHull, collisionIgnoreTimer: 0.05f);
1429 if (shrapnelParticle ==
null || sparkParticle ==
null) {
break; }
1435 partial
void OnHealthChangedProjSpecific(Character attacker,
float damageAmount);
1439 if (
Bodies ==
null)
return;
1440 foreach (Body body
in Bodies)
1442 body.CollisionCategories = collisionCategory;
1446 private void UpdateSections()
1448 if (
Bodies ==
null) {
return; }
1449 foreach (Body b
in Bodies)
1451 GameMain.World.Remove(b);
1454 bodyDimensions.Clear();
1456 convexHulls?.ForEach(ch => ch.Remove());
1457 convexHulls?.Clear();
1460 bool hasHoles =
false;
1461 var mergedSections =
new List<WallSection>();
1462 for (
int i = 0; i <
Sections.Length; i++ )
1469 if (!mergedSections.Any()) {
continue; }
1470 var mergedRect = GenerateMergedRect(mergedSections);
1471 mergedSections.Clear();
1472 CreateRectBody(mergedRect, createConvexHull:
true);
1481 if (mergedSections.Count > 0)
1483 var mergedRect = GenerateMergedRect(mergedSections);
1484 CreateRectBody(mergedRect, createConvexHull:
true);
1489 if (hasHoles || !
Bodies.Any())
1491 Body sensorBody = CreateRectBody(
rect, createConvexHull:
false);
1492 sensorBody.CollisionCategories = Physics.CollisionRepairableWall;
1497 bool intersectsWithBody =
false;
1498 foreach (var body
in Bodies)
1501 ConvertUnits.ToDisplayUnits(body.Position - bodyDimensions[body] / 2).ToPoint(),
1502 ConvertUnits.ToDisplayUnits(bodyDimensions[body]).ToPoint());
1505 sectionRect.Y -= section.rect.Height;
1506 if (bodyRect.Intersects(sectionRect))
1508 intersectsWithBody =
true;
1512 section.NoPhysicsBody = !intersectsWithBody;
1516 private Body CreateRectBody(Rectangle
rect,
bool createConvexHull)
1518 float diffFromCenter;
1521 diffFromCenter = (
rect.Center.X - this.rect.Center.X) / (
float)this.rect.Width *
BodyWidth;
1524 if (
FlippedX) { diffFromCenter = -diffFromCenter; }
1528 diffFromCenter = ((
rect.Y -
rect.Height / 2) - (this.rect.Y -
this.rect.Height / 2)) / (
float)this.rect.Height *
BodyHeight;
1531 if (
FlippedY) { diffFromCenter = -diffFromCenter; }
1534 Vector2 bodyOffset = ConvertUnits.ToSimUnits(
BodyOffset) * scale;
1536 Body newBody = GameMain.World.CreateRectangle(
1537 ConvertUnits.ToSimUnits(
rect.Width),
1538 ConvertUnits.ToSimUnits(
rect.Height),
1540 bodyType: BodyType.Static,
1541 findNewContacts:
false);
1542 newBody.Friction = 0.5f;
1543 newBody.OnCollision += OnWallCollision;
1544 newBody.CollisionCategories = (
Prefab.
Platform) ? Physics.CollisionPlatform : Physics.CollisionWall;
1545 newBody.UserData =
this;
1547 Vector2 structureCenter = ConvertUnits.ToSimUnits(
Position);
1550 Vector2 pos = structureCenter + bodyOffset +
new Vector2(
1553 * ConvertUnits.ToSimUnits(diffFromCenter);
1554 newBody.SetTransformIgnoreContacts(ref pos, -
BodyRotation);
1558 Vector2 pos = structureCenter + (
IsHorizontal ? Vector2.UnitX : Vector2.UnitY) * ConvertUnits.ToSimUnits(diffFromCenter) + bodyOffset;
1559 newBody.SetTransformIgnoreContacts(ref pos, newBody.Rotation);
1562 if (createConvexHull)
1564 CreateConvexHull(ConvertUnits.ToDisplayUnits(newBody.Position),
rect.Size.ToVector2(), newBody.Rotation);
1568 bodyDimensions.Add(newBody,
new Vector2(ConvertUnits.ToSimUnits(
rect.Width), ConvertUnits.ToSimUnits(
rect.Height)));
1573 partial
void CreateConvexHull(Vector2 position, Vector2 size,
float rotation);
1575 public override void FlipX(
bool relativeToSub)
1577 base.FlipX(relativeToSub);
1580 if (
Prefab.CanSpriteFlipX)
1591 bodyDimensions.Clear();
1593 CreateStairBodies();
1603 public override void FlipY(
bool relativeToSub)
1605 base.FlipY(relativeToSub);
1608 if (
Prefab.CanSpriteFlipY)
1619 bodyDimensions.Clear();
1621 CreateStairBodies();
1639 DebugConsole.ThrowError(
"Error loading structure - structure prefab \"" + name +
"\" (identifier \"" + identifier +
"\") not found.");
1652 if (submarine?.Info.GameVersion !=
null)
1665 targetSize:
rect.Size.ToVector2(),
1666 originalTextureOffset:
1669 submarineInfo: submarine.
Info,
1673 flippedY: flippedY);
1677 bool hasDamage =
false;
1678 foreach (var subElement
in element.Elements())
1680 switch (subElement.Name.ToString().ToLowerInvariant())
1683 int index = subElement.GetAttributeInt(
"i", -1);
1684 if (index == -1) {
continue; }
1686 if (index < 0 || index >= s.SectionCount)
1688 string errorMsg = $
"Error while loading structure \"{s.Name}\". Section damage index out of bounds. Index: {index}, section count: {s.SectionCount}.";
1689 DebugConsole.ThrowError(errorMsg);
1690 GameAnalyticsManager.AddErrorEventOnce(
"Structure.Load:SectionIndexOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1694 float damage = subElement.GetAttributeFloat(
"damage", 0.0f);
1695 s.Sections[index].damage = damage;
1696 hasDamage |= damage > 0.0f;
1701 var upgradeIdentifier = subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
1702 UpgradePrefab upgradePrefab = UpgradePrefab.Find(upgradeIdentifier);
1703 int level = subElement.GetAttributeInt(
"level", 1);
1704 if (upgradePrefab !=
null)
1706 s.AddUpgrade(
new Upgrade(s, upgradePrefab, level, subElement));
1710 DebugConsole.ThrowError($
"An upgrade with identifier \"{upgradeIdentifier}\" on {s.Name} was not found. " +
1711 "It's effect will not be applied and won't be saved after the round ends.");
1719 if (flippedY) { s.FlipY(
false); }
1724 s.UseDropShadow = prefab.Body;
1727 if (element.GetAttribute(nameof(
NoAITarget)) ==
null)
1729 s.NoAITarget = prefab.NoAITarget;
1743 if (identifier.IsEmpty)
1760 public override XElement
Save(XElement parentElement)
1762 XElement element =
new XElement(
"Structure");
1768 new XAttribute(
"name", base.Prefab.Name),
1769 new XAttribute(
"identifier", base.Prefab.Identifier),
1770 new XAttribute(
"ID",
ID),
1771 new XAttribute(
"rect",
1774 width +
"," + height));
1776 if (
FlippedX) { element.Add(
new XAttribute(
"flippedx",
true)); }
1777 if (
FlippedY) { element.Add(
new XAttribute(
"flippedy",
true)); }
1779 for (
int i = 0; i <
Sections.Length; i++)
1781 if (
Sections[i].damage == 0.0f) {
continue; }
1782 var sectionElement =
1783 new XElement(
"section",
1784 new XAttribute(
"i", i),
1785 new XAttribute(
"damage",
Sections[i].damage));
1786 element.Add(sectionElement);
1793 element.GetAttribute(nameof(
CastShadow))?.Remove();
1798 upgrade.Save(element);
1801 parentElement.Add(element);
1808 for (
int i = 0; i <
Sections.Length; i++)
1810 SetDamage(i,
Sections[i].damage, createNetworkEvent:
false, createExplosionEffect:
false);
AbilityAttackerSubmarine(Character character, Submarine submarine)
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float GetStructureDamage(float deltaTime)
bool EmitStructureDamageParticles
float DamageMultiplier
Used for multiplying all the damage.
bool CreateWallDamageProjectiles
string StructureSoundType
readonly Dictionary< Affliction, XElement > Afflictions
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
static readonly List< Character > CharacterList
Vector2 GetAttributeVector2(string key, in Vector2 def)
bool GetAttributeBool(string key, bool def)
Rectangle GetAttributeRect(string key, in Rectangle def)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
static EntitySpawner Spawner
virtual Vector2 WorldPosition
const ushort NullEntityID
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
void FreeID()
Removes the entity from the entity dictionary and frees up the ID it was using.
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition=null, int? quality=null, Action< Item > onSpawned=null)
Explosions are area of effect attacks that can damage characters, items and structures.
bool DistanceFalloff
Does the damage from the explosion decrease with distance from the origin of the explosion?
float CameraShake
Intensity of the screen shake effect.
bool IgnoreCover
When set to true, the explosion don't deal less damage when the target is behind a solid object.
float CameraShakeRange
How far away does the camera shake effect reach.
readonly HashSet< Character > IgnoredCharacters
void Explode(Vector2 worldPosition, Entity damageSource, Character attacker=null)
bool PlayDamageSounds
Should the normal damage sounds be played when the explosion damages something. Usually disabled.
IEnumerable< Structure > IgnoredCover
Structures that don't count as "cover" that reduces damage from the explosion. Only relevant if Ignor...
bool OnlyInside
Whether the explosion only affects characters inside a submarine.
static GameSession?? GameSession
static NetworkMember NetworkMember
static ParticleManager ParticleManager
static void StructureDamaged(Structure structure, float damageAmount, Character character)
ushort GetOffsetId(XElement element)
readonly int InitialDepth
The depth at which the level starts at, in in-game coordinates. E.g. if this was set to 100 000 (= 10...
const float DefaultRealWorldCrushDepth
Vector2 LightTextureTargetSize
Vector2 LightTextureScale
static readonly List< MapEntity > MapEntityList
override Vector2 Position
readonly List< MapEntity > linkedTo
readonly List< Upgrade > Upgrades
List of upgrades this item has
Upgrade GetUpgrade(Identifier identifier)
static MapEntityPrefab Find(string name, string identifier=null, bool showErrorMessages=true)
Find a matching map entity prefab
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static void UpgradeGameVersion(ISerializableEntity entity, ContentXElement configElement, Version savedVersion)
Upgrade the properties of an entity saved with an older version of the game. Properties that should b...
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
float SkillIncreasePerRepairedStructureDamage
static SkillSettings Current
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
void ReloadXML()
Works only if there is a name attribute defined for the sprite. For items and structures,...
new StructurePrefab Prefab
override void FlipX(bool relativeToSub)
Flip the entity horizontally
override void Move(Vector2 amount, bool ignoreContacts=true)
override bool DrawOverWater
override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent=false)
Adds a new upgrade to the item
ImmutableHashSet< Identifier > Tags
static Structure Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
float BodyRotation
In radians, takes flipping into account
override void Update(float deltaTime, Camera cam)
Vector2 BodyOffset
Offset of the physics body from the center of the structure. Takes flipping into account.
Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine, ushort id=Entity.NullEntityID, XElement element=null)
readonly List< LightSource > Lights
Dictionary< Identifier, SerializableProperty > SerializableProperties
bool SectionBodyDisabled(int sectionIndex)
override void OnMapLoaded()
bool SectionIsLeaking(int sectionIndex)
Sections that are leaking have a gap placed on them
override XElement Save(XElement parentElement)
void SetDamage(int sectionIndex, float damage, Character attacker=null, bool createNetworkEvent=true, bool isNetworkEvent=true, bool createExplosionEffect=true, bool createWallDamageProjectiles=false)
void SetCollisionCategory(Category collisionCategory)
float SectionDamage(int sectionIndex)
override Quad2D GetTransformedQuad()
Vector2 SectionPosition(int sectionIndex, bool world=false)
const int WallSectionSize
SpriteEffects SpriteEffects
override bool IsMouseOn(Vector2 position)
bool AllSectionBodiesDisabled()
static Vector2 UpgradeTextureOffset(Vector2 targetSize, Vector2 originalTextureOffset, SubmarineInfo submarineInfo, Rectangle sourceRect, Vector2 scale, bool flippedX, bool flippedY)
override void FlipY(bool relativeToSub)
Flip the entity vertically
void AddDamage(int sectionIndex, float damage, Character attacker=null, bool emitParticles=true, bool createWallDamageProjectiles=false)
static Structure GetAttachTarget(Vector2 worldPosition)
Checks if there's a structure items can be attached to at the given position and returns it.
override bool DrawBelowWater
override void ShallowRemove()
Remove the entity from the entity list without removing links to other entities
override string ToString()
float ScaleWhenTextureOffsetSet
int SectionLength(int sectionIndex)
int FindSectionIndex(Vector2 displayPos, bool world=false, bool clamp=false)
bool SectionIsLeakingFromOutside(int sectionIndex)
Rectangle DefaultRect
Unscaled rect
static List< Structure > WallList
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound=false)
static StructurePrefab FindPrefab(string name, Identifier identifier)
override MapEntity Clone()
WallSection GetSection(int sectionIndex)
readonly ContentXElement ConfigElement
readonly? bool IsHorizontal
If null, the orientation is determined automatically based on the dimensions of the structure instanc...
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
Vector2 HiddenSubPosition
override Vector2? Position
bool IgnoreByAI(Character character)
WallSection(Rectangle rect, Structure wall, float damage=0.0f)
Interface for entities that the server can send events to the clients