3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
7 using FarseerPhysics.Dynamics;
17 private static readonly HashSet<Door> doorList =
new HashSet<Door>();
19 public static IReadOnlyCollection<Door>
DoorList {
get {
return doorList; } }
21 private Gap linkedGap;
24 private float openState;
25 private readonly
Sprite doorSprite, weldedSprite, brokenSprite;
26 private readonly
bool scaleBrokenSprite, fadeBrokenSprite;
27 private readonly
bool autoOrientGap;
29 private bool isJammed;
32 get {
return isJammed; }
35 if (isJammed == value) {
return; }
38 item.CreateServerEvent(
this);
48 get {
return isStuck; }
51 if (isStuck == value) {
return; }
56 item.CreateServerEvent(
this);
63 const float StuckReductionOnOpen = 30.0f;
65 private float resetPredictionTimer;
66 private float toggleCooldownTimer;
69 private float damageSoundCooldown;
71 private double lastBrokenTime;
75 private bool isBroken;
81 get {
return isBroken; }
84 if (isBroken == value) {
return; }
95 item.CreateServerEvent(
this);
105 private float RepairThreshold
113 [
Serialize(0.0f,
IsPropertySaveable.Yes, description:
"How badly stuck the door is (in percentages). If the percentage reaches 100, the door needs to be cut open to make it usable again.")]
116 get {
return stuck; }
119 if (isOpen || isBroken || !
CanBeWelded) {
return; }
120 stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
123 if (stuck <= 0.0f) {
IsStuck =
false; }
124 if (stuck >= 99.0f) {
IsStuck =
true; }
134 [
Serialize(1.0f,
IsPropertySaveable.Yes, description:
"The door cannot be opened/closed during this time after it has been opened/closed by another character."),
Editable]
143 if (linkedGap ==
null)
151 private void GetLinkedGap()
154 if (linkedGap ==
null)
170 [
Serialize(
"0.0,0.0,0.0,0.0",
IsPropertySaveable.No, description:
"Position and size of the window on the door. The upper left corner is 0,0. Set the width and height to 0 if you don't want the door to have a window.")]
176 get {
return isOpen; }
189 get {
return isOpen; }
207 [
Serialize(
false,
IsPropertySaveable.No, description:
"If the door has integrated buttons, it can be opened by interacting with it directly (instead of using buttons wired to it).")]
211 Serialize(
true,
IsPropertySaveable.No, description:
"If the door has integrated buttons, should clicking on it perform the default action of opening the door? Can be used in conjunction with the \"activate_out\" output to pass a signal to a circuit without toggling the door when someone tries to open/close the door.")]
216 get {
return openState; }
219 openState = MathHelper.Clamp(value, 0.0f, 1.0f);
224 if (Math.Abs(lastConvexHullState - openState) * size > 5.0f ||
225 (openState <= 0.0f && lastConvexHullState > 0.0f) ||
226 (openState >= 1.0f && lastConvexHullState < 1.0f))
229 lastConvexHullState = openState;
235 [
Serialize(
false,
IsPropertySaveable.No, description:
"Characters and items cannot pass through impassable doors. Useful for things such as ducts that should only let water and air through.")]
249 : base(
item, element)
257 foreach (var subElement
in element.Elements())
260 switch (subElement.Name.ToString().ToLowerInvariant())
263 doorSprite =
new Sprite(subElement, path: textureDir);
266 weldedSprite =
new Sprite(subElement, path: textureDir);
269 brokenSprite =
new Sprite(subElement, path: textureDir);
270 scaleBrokenSprite = subElement.GetAttributeBool(
"scale",
false);
271 fadeBrokenSprite = subElement.GetAttributeBool(
"fade",
false);
290 ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)),
291 ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)),
295 Physics.CollisionWall,
296 Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile,
297 findNewContacts:
false)
303 ConvertUnits.ToSimUnits(
new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
311 public override void Move(Vector2 amount,
bool ignoreContacts =
false)
326 private readonly
LocalizedString accessDeniedTxt = TextManager.Get(
"AccessDenied");
327 private readonly
LocalizedString cannotOpenText = TextManager.Get(
"DoorMsgCannotOpen");
330 Msg =
HasAccess(character) ?
"ItemMsgOpen" :
"ItemMsgForceOpenCrowbar";
336 return isBroken || base.HasRequiredItems(character, addMessage, msg);
359 if (toggleCooldownTimer > 0.0f && user != lastUser)
373 if (
IsStuck) { HintManager.OnTryOpenStuckDoor(user); }
375 toggleCooldownTimer = 1.0f;
389 if (isBroken) {
return true; }
403 GUI.AddMessage(accessDeniedTxt, GUIStyle.Red);
421 position.Y >=
item.
Rect.Y - maxPerpendicularDistance &&
429 position.X >=
item.
Rect.X - maxPerpendicularDistance &&
430 position.X <=
item.
Rect.Right + maxPerpendicularDistance;
436 UpdateProjSpecific(deltaTime);
437 toggleCooldownTimer -= deltaTime;
438 damageSoundCooldown -= deltaTime;
442 lastBrokenTime = Timing.TotalTime;
455 bool isClosing =
false;
461 isClosing = openState > 0.0f && openState < 1.0f && !isOpen;
466 isClosing = openState > 0.0f && openState < 1.0f && !(bool)
PredictedState;
468 resetPredictionTimer -= deltaTime;
469 if (resetPredictionTimer <= 0.0f)
479 if (
OpenState < 0.9f) { PushCharactersAway(); }
480 if (CheckSubmarinesInDoorWay())
512 partial
void UpdateProjSpecific(
float deltaTime);
517 base.UpdateBroken(deltaTime, cam);
524 private void EnableBody()
530 while (ce !=
null && ce.Contact !=
null)
532 ce.Contact.Enabled =
false;
535 PushCharactersAway();
547 private void DisableBody()
555 while (ce !=
null && ce.Contact !=
null)
557 ce.Contact.Enabled =
false;
566 if (linkedGap !=
null)
568 linkedGap.
Open = 1.0f;
573 if (convexHull !=
null) { convexHull.
Enabled =
false; }
574 if (convexHull2 !=
null) { convexHull2.
Enabled =
false; }
608 if (linkedGap !=
null)
617 base.RemoveComponentSpecific();
645 doorSprite?.Remove();
646 weldedSprite?.Remove();
653 doorList.Remove(
this);
656 private bool CheckSubmarinesInDoorWay()
658 if (linkedGap !=
null && linkedGap.
IsRoomToRoom) {
return false; }
663 doorRect.Width = (int)(
item.
Rect.Width * (1.0f - openState));
667 doorRect.Height = (int)(
item.
Rect.Height * (1.0f - openState));
670 foreach (Submarine sub
in Submarine.Loaded)
674 worldBorders.Location += sub.WorldPosition.ToPoint();
675 if (!
Submarine.RectsOverlap(worldBorders, doorRect)) {
continue; }
676 foreach (Hull hull
in sub.GetHulls(alsoFromConnectedSubs:
false))
678 if (
Submarine.RectsOverlap(hull.WorldRect, doorRect)) {
return true; }
684 bool itemPosErrorShown;
685 private readonly HashSet<Character> characterPosErrorShown =
new HashSet<Character>();
686 private void PushCharactersAway()
690 if (!itemPosErrorShown)
692 DebugConsole.ThrowError(
"Failed to push a character out of a doorway - position of the door is not valid (" +
item.
SimPosition +
")");
693 GameAnalyticsManager.AddErrorEventOnce(
"PushCharactersAway:DoorPosInvalid", GameAnalyticsManager.ErrorSeverity.Error,
694 "Failed to push a character out of a doorway - position of the door is not valid (" +
item.
SimPosition +
").");
695 itemPosErrorShown =
true;
700 Vector2 simPos = ConvertUnits.ToSimUnits(
new Vector2(
item.
Rect.X,
item.
Rect.Y));
703 new Vector2(
item.
Rect.Width * (1.0f - openState), doorSprite.size.Y *
item.
Scale) :
704 new Vector2(doorSprite.size.X *
item.Scale,
item.Rect.Height * (1.0f - openState));
705 Vector2 simSize = ConvertUnits.ToSimUnits(currSize);
707 foreach (Character c
in Character.CharacterList)
709 if (!c.Enabled) {
continue; }
710 if (!MathUtils.IsValid(c.SimPosition))
712 if (!characterPosErrorShown.Contains(c))
714 if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(
"Failed to push a character out of a doorway - position of the character \"" + c.Name +
"\" is not valid (" + c.SimPosition +
")"); }
715 GameAnalyticsManager.AddErrorEventOnce(
"PushCharactersAway:CharacterPosInvalid", GameAnalyticsManager.ErrorSeverity.Error,
716 "Failed to push a character out of a doorway - position of the character \"" + c.SpeciesName +
"\" is not valid (" + c.SimPosition +
")." +
717 " Removed: " + c.Removed +
718 " Remoteplayer: " + c.IsRemotePlayer);
719 characterPosErrorShown.Add(c);
725 foreach (Limb limb
in c.AnimController.Limbs)
727 if (limb.IsSevered) {
continue; }
728 if (PushBodyOutOfDoorway(c, limb.body, dir, simPos, simSize) && damageSoundCooldown <= 0.0f)
731 SoundPlayer.PlayDamageSound(
"LimbBlunt", 1.0f, limb.body);
733 damageSoundCooldown = 0.5f;
736 PushBodyOutOfDoorway(c, c.AnimController.Collider, dir, simPos, simSize);
740 private bool PushBodyOutOfDoorway(Character c, PhysicsBody body,
int dir, Vector2 doorRectSimPos, Vector2 doorRectSimSize)
742 if (!MathUtils.IsValid(body.SimPosition))
744 DebugConsole.ThrowError(
"Failed to push a limb out of a doorway - position of the body (character \"" + c.Name +
"\") is not valid (" + body.SimPosition +
")");
745 GameAnalyticsManager.AddErrorEventOnce(
"PushCharactersAway:LimbPosInvalid", GameAnalyticsManager.ErrorSeverity.Error,
746 "Failed to push a character out of a doorway - position of the character \"" + c.SpeciesName +
"\" is not valid (" + body.SimPosition +
")." +
747 " Removed: " + c.Removed +
748 " Remoteplayer: " + c.IsRemotePlayer);
755 if (body.SimPosition.X < doorRectSimPos.X || body.SimPosition.X > doorRectSimPos.X + doorRectSimSize.X) {
return false; }
760 if (body.SimPosition.Y > doorRectSimPos.Y || body.SimPosition.Y < doorRectSimPos.Y - doorRectSimSize.Y) {
return false; }
766 if (Math.Sign(diff) != dir)
770 body.SetTransform(
new Vector2(body.SimPosition.X,
item.
SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation);
774 body.SetTransform(
new Vector2(
item.
SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
781 if (Math.Abs(body.SimPosition.Y -
item.
SimPosition.Y) > doorRectSimSize.Y * 0.5f) {
return false; }
782 body.ApplyLinearImpulse(
new Vector2(isOpen ? 0.0f : 1.0f, dir * 2.0f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
786 if (Math.Abs(body.SimPosition.X -
item.
SimPosition.X) > doorRectSimSize.X * 0.5f) {
return false; }
787 body.ApplyLinearImpulse(
new Vector2(dir * 2.0f, isOpen ? 0.0f : -1.0f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
792 if (lastBrokenTime < Timing.TotalTime - 1.0f)
799 partial
void OnFailedToOpen();
806 return base.HasAccess(character);
810 return base.HasAccess(character) &&
Item.GetConnectedComponents<
Controller>(
true).
Any(b => b.HasAccess(character));
820 if (connection.
Name ==
"toggle")
822 if (signal.
value ==
"0") {
return; }
823 if (toggleCooldownTimer > 0.0f && signal.
sender != lastUser) { OnFailedToOpen();
return; }
824 if (
IsStuck) { toggleCooldownTimer = 1.0f; OnFailedToOpen();
return; }
827 SetState(!wasOpen,
false,
true, forcedOpen:
false);
829 else if (connection.
Name ==
"set_state")
831 bool signalOpen = signal.
value !=
"0";
832 if (
IsStuck && signalOpen != wasOpen) { toggleCooldownTimer = 1.0f; OnFailedToOpen();
return; }
833 SetState(signalOpen,
false,
true, forcedOpen:
false);
837 if (signal.
sender !=
null && wasOpen != isOpen)
844 public void TrySetState(
bool open,
bool isNetworkMessage,
bool sendNetworkMessage =
false)
846 SetState(open, isNetworkMessage, sendNetworkMessage, forcedOpen:
false);
849 partial
void SetState(
bool open,
bool isNetworkMessage,
bool sendNetworkMessage,
bool forcedOpen);
static readonly List< Character > CharacterList
static Character? Controlled
readonly AnimController AnimController
bool GetAttributeBool(string key, bool def)
virtual Vector2 WorldPosition
static NetworkMember NetworkMember
static List< Gap > GapList
override Vector2? SimPosition
float MaxRepairConditionMultiplier
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
bool FullyInitialized
Has everything in the item been loaded/instantiated/initialized (basically, can be used to check if t...
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
float ConditionPercentage
void SendSignal(string signal, string connectionName)
override void Move(Vector2 amount, bool ignoreContacts=false)
override bool OnPicked(Character picker)
override void OnScaleChanged()
Fixture OutsideSubmarineFixture
override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
override void UpdateBroken(float deltaTime, Camera cam)
override void OnMapLoaded()
Called when all items have been loaded. Use to initialize connections between items.
override void Update(float deltaTime, Camera cam)
bool IsPositionOnWindow(Vector2 position, float maxPerpendicularDistance=10.0f)
Is the given position inside the vertical bounds of the window, and roughly on the door horizontally?...
override bool Pick(Character picker)
a Character has picked the item
override bool Select(Character character)
Door(Item item, ContentXElement element)
bool HasIntegratedButtons
override void ReceiveSignal(Signal signal, Connection connection)
bool UseBetweenOutpostModules
override void RemoveComponentSpecific()
override bool HasAccess(Character character)
Only checks if any of the Picked requirements are matched (used for checking id card(s))....
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
void TrySetState(bool open, bool isNetworkMessage, bool sendNetworkMessage=false)
bool ShouldBeOpen
Can be used by status effects to tell the door to open (setting IsOpen directly would make it immedia...
bool IsConvexHullHorizontal
static IReadOnlyCollection< Door > DoorList
virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
string GetTextureDirectory(ContentXElement subElement)
Dictionary< RelatedItem.RelationType, List< RelatedItem > > RequiredItems
List< InvSlotType > allowedSlots
void StopPicking(Character picker)
readonly List< MapEntity > linkedTo
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void ForceRefreshFloorY()
const Category CollidesWith
IEnumerable< Submarine > DockedTo
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.