Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs
Markus Isberg 6be757a45b Build 0.18.5.0
2022-06-03 22:29:04 +09:00

338 lines
13 KiB
C#

using Barotrauma.Extensions;
using Barotrauma.Lights;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma.Items.Components
{
partial class Door : Pickable, IDrawableComponent, IServerSerializable
{
private ConvexHull convexHull;
private ConvexHull convexHull2;
private float shake;
private float shakeTimer;
private Vector2 shakePos;
//openState when the vertices of the convex hull were last calculated
private float lastConvexHullState;
[Serialize("1,1", IsPropertySaveable.No, description: "The scale of the shadow-casting area of the door (relative to the actual size of the door).")]
public Vector2 ShadowScale
{
get;
set;
}
public Vector2 DrawSize
{
//use the extents of the item as the draw size
get { return Vector2.Zero; }
}
private Vector2[] GetConvexHullCorners(Rectangle rect)
{
Point shadowSize = rect.Size.Multiply(ShadowScale);
Vector2 center = new Vector2(rect.Center.X, rect.Y - rect.Height / 2);
Vector2[] corners = new Vector2[4];
corners[0] = center + new Vector2(-shadowSize.X, -shadowSize.Y) / 2;
corners[1] = center + new Vector2(-shadowSize.X, shadowSize.Y) / 2;
corners[2] = center + new Vector2(shadowSize.X, shadowSize.Y) / 2;
corners[3] = center + new Vector2(shadowSize.X, -shadowSize.Y) / 2;
if (IsHorizontal)
{
if (item.FlippedX)
{
Vector2 itemCenter = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2);
for (int i = 0; i < corners.Length; i++)
{
corners[i].X = itemCenter.X * 2 - corners[i].X;
}
Array.Reverse(corners);
}
}
else
{
if (item.FlippedY)
{
Vector2 itemCenter = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2);
for (int i = 0; i < corners.Length; i++)
{
corners[i].Y = itemCenter.Y * 2 - corners[i].Y;
}
Array.Reverse(corners);
}
}
return corners;
}
private void UpdateConvexHulls()
{
if (item.Removed) { return; }
doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
(int)(doorSprite.size.X * item.Scale),
(int)(doorSprite.size.Y * item.Scale));
Rectangle rect = doorRect;
if (IsHorizontal)
{
rect.Width = (int)(rect.Width * (1.0f - openState));
}
else
{
rect.Height = (int)(rect.Height * (1.0f - openState));
}
//only merge the door's convex hull with overlapping wall segments if it's fully open or fully closed
//it's the heaviest part of changing the convex hull, and doesn't need to be done while the door is still in motion
bool mergeOverlappingSegments = openState <= 0.0f || openState >= 1.0f;
if (Window.Height > 0 && Window.Width > 0)
{
if (IsHorizontal)
{
rect.Width = (int)(Window.X * item.Scale);
rect.X -= (int)(doorRect.Width * openState);
rect.Width = Math.Max(rect.Width - (doorRect.X - rect.X), 0);
rect.X = Math.Max(doorRect.X, rect.X);
if (convexHull2 != null)
{
Rectangle rect2 = doorRect;
rect2.X += (int)(Window.Right * item.Scale);
rect2.X -= (int)(doorRect.Width * openState);
rect2.X = Math.Max(doorRect.X, rect2.X);
rect2.Width = doorRect.Right - (int)(doorRect.Width * openState) - rect2.X;
if (rect2.Width == 0)
{
convexHull2.Enabled = false;
}
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
}
}
}
else
{
rect.Height = -(int)(Window.Y * item.Scale);
rect.Y += (int)(doorRect.Height * openState);
rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
rect.Y = Math.Min(doorRect.Y, rect.Y);
if (convexHull2 != null)
{
Rectangle rect2 = doorRect;
rect2.Y += (int)(Window.Y * item.Scale - Window.Height * item.Scale);
rect2.Y += (int)(doorRect.Height * openState);
rect2.Y = Math.Min(doorRect.Y, rect2.Y);
rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
if (rect2.Height == 0)
{
convexHull2.Enabled = false;
}
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
}
}
}
}
if (convexHull == null) { return; }
if (rect.Height == 0 || rect.Width == 0)
{
convexHull.Enabled = false;
}
else
{
convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect), mergeOverlappingSegments);
}
}
partial void UpdateProjSpecific(float deltaTime)
{
if (shakeTimer > 0.0f)
{
shakeTimer -= deltaTime;
Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0.5f) - 0.5f);
shakePos = noisePos * shake * 2.0f;
shake = Math.Min(shake, shakeTimer * 10.0f);
}
else
{
shakePos = Vector2.Zero;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
Color color = item.SpriteColor;
if (brokenSprite == null)
{
//broken doors turn black if no broken sprite has been configured
color *= (item.Condition / item.MaxCondition);
color.A = 255;
}
if (stuck > 0.0f && weldedSprite != null)
{
Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2.0f) + shakePos;
if (item.Submarine != null) { weldSpritePos += item.Submarine.DrawPosition; }
weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch,
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale);
}
if (openState >= 1.0f) { return; }
Vector2 pos;
if (IsHorizontal)
{
pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height / 2);
if (item.FlippedX) { pos.X += (int)(doorSprite.size.X * item.Scale * openState); }
}
else
{
pos = new Vector2(item.Rect.Center.X, item.Rect.Y);
if (item.FlippedY) { pos.Y -= (int)(doorSprite.size.Y * item.Scale * openState); }
}
pos += shakePos;
if (item.Submarine != null) { pos += item.Submarine.DrawPosition; }
pos.Y = -pos.Y;
if (brokenSprite == null || !IsBroken)
{
spriteBatch.Draw(doorSprite.Texture, pos,
getSourceRect(doorSprite, openState, IsHorizontal),
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth);
}
if (brokenSprite != null && item.Health < item.MaxCondition)
{
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.MaxCondition) : Vector2.One;
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos,
getSourceRect(brokenSprite, openState, IsHorizontal),
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, item.SpriteEffects,
brokenSprite.Depth);
}
static Rectangle getSourceRect(Sprite sprite, float openState, bool horizontal)
{
if (horizontal)
{
return new Rectangle(
(int)(sprite.SourceRect.X + sprite.size.X * openState),
sprite.SourceRect.Y,
(int)(sprite.size.X * (1.0f - openState)),
(int)sprite.size.Y);
}
else
{
return new Rectangle(
sprite.SourceRect.X,
(int)(sprite.SourceRect.Y + sprite.size.Y * openState),
(int)sprite.size.X,
(int)(sprite.size.Y * (1.0f - openState)));
}
}
}
partial void OnFailedToOpen()
{
if (shakeTimer <= 0.0f)
{
PlaySound(ActionType.OnFailure);
shake = 5.0f;
shakeTimer = 1.0f;
}
}
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
{
if ((IsStuck && !isNetworkMessage) ||
(PredictedState == null && isOpen == open) ||
(PredictedState != null && isOpen == PredictedState.Value && isOpen == open))
{
return;
}
if (GameMain.Client != null && !isNetworkMessage)
{
bool stateChanged = open != PredictedState;
//clients can "predict" that the door opens/closes when a signal is received
//the prediction will be reset after 1 second, setting the door to a state
//sent by the server, or reverting it back to its old state if no msg from server was received
PredictedState = open;
resetPredictionTimer = CorrectionDelay;
if (stateChanged) PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse);
}
else
{
isOpen = open;
if (!isNetworkMessage || open != PredictedState)
{
StopPicking(null);
ActionType actionType = ActionType.OnUse;
if (forcedOpen)
{
actionType = ActionType.OnPicked;
}
else
{
if (open && HasSoundsOfType[(int)ActionType.OnOpen])
{
actionType = ActionType.OnOpen;
}
else if (!open && HasSoundsOfType[(int)ActionType.OnClose])
{
actionType = ActionType.OnClose;
}
}
PlaySound(actionType);
if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); }
}
}
}
public override void ClientEventRead(IReadMessage msg, float sendingTime)
{
base.ClientEventRead(msg, sendingTime);
bool open = msg.ReadBoolean();
bool broken = msg.ReadBoolean();
bool forcedOpen = msg.ReadBoolean();
bool isStuck = msg.ReadBoolean();
bool isJammed = msg.ReadBoolean();
SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen);
stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
UInt16 lastUserID = msg.ReadUInt16();
Character user = lastUserID == 0 ? null : Entity.FindEntityByID(lastUserID) as Character;
if (user != lastUser)
{
lastUser = user;
toggleCooldownTimer = ToggleCoolDown;
}
this.isStuck = isStuck;
this.isJammed = isJammed;
if (isStuck) { OpenState = 0.0f; }
IsBroken = broken;
PredictedState = null;
}
}
}